Coverage for lobster/tools/cpp/cpp.py: 0%
87 statements
« prev ^ index » next coverage.py v7.10.7, created at 2026-05-12 15:02 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2026-05-12 15:02 +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 = f"^{FILE_LINE_PATTERN} warning:"
36SUFFIX = r"\[lobster-tracing\]$"
38RE_NOTAGS = (PREFIX + " " +
39 rf"{KIND_PATTERN} {NAME_PATTERN} has no tracing tags" +
40 " " + SUFFIX)
41RE_TAGS = (PREFIX + " " +
42 rf"{KIND_PATTERN} {NAME_PATTERN} traces to +([^,\n]+(?:\s*,\s*[^,\n]+)*) +" +
43 SUFFIX)
44RE_JUST = (PREFIX + " " +
45 rf"{KIND_PATTERN} {NAME_PATTERN} exempt from tracing: +(.+) +" +
46 SUFFIX)
49def extract_clang_finding_name(line: str) -> Optional[str]:
50 """extracts the name of the clang finding from the end of the line"""
51 if line.endswith("]") and ("[" in line):
52 return line.split("[")[-1].rstrip("]")
53 return None
56class CppTool(MultiFileInputTool):
57 def __init__(self):
58 super().__init__(
59 name = "cpp",
60 description = "Extract tracing tags from C++ using clang-tidy",
61 extensions=("cpp", "cc", "c", "h"),
62 official = True,
63 )
64 self._argument_parser.add_argument(
65 "--clang-tidy",
66 default="clang-tidy",
67 metavar="FILE",
68 help=("use the specified clang-tidy; by default we"
69 " pick the one on PATH"),
70 )
71 self._argument_parser.add_argument(
72 "--compile-commands",
73 metavar="FILE",
74 default=None,
75 help=("Path to the compile command database for all targets for "
76 "'clang tidy', or none to use the default behavior of "
77 "'clang tidy'. This is equal to calling 'clang tidy' "
78 "directly with its '-p' option. Refer to its official "
79 "documentation for more details."),
80 )
81 self._argument_parser.add_argument(
82 "--skip-clang-errors",
83 default=[],
84 nargs="*",
85 metavar="FINDINGS",
86 help="List of all clang-tidy errors to ignore.",
87 )
89 def _add_config_argument(self):
90 # This tool does not use a config file
91 pass
93 def _run_impl(self, options: Namespace) -> int:
94 config = Config(
95 inputs=None,
96 inputs_from_file=None,
97 extensions=self._extensions,
98 exclude_patterns=None,
99 schema=Implementation,
100 )
101 file_list = create_worklist(config, options.dir_or_files)
102 clang_tidy_path = os.path.expanduser(options.clang_tidy)
104 # Test if the clang-tidy can be used
105 rv = subprocess.run(
106 [
107 clang_tidy_path,
108 "-checks=-*,lobster-tracing",
109 "--list-checks",
110 ],
111 stdout=subprocess.PIPE,
112 stderr=subprocess.PIPE,
113 encoding="UTF-8",
114 check=False,
115 )
117 if "No checks enabled." in rv.stderr:
118 print("The provided clang-tidy does include the lobster-tracing check")
119 print("> Please build from "
120 "https://github.com/bmw-software-engineering/llvm-project")
121 print("> Or make sure to provide the "
122 "correct binary using the --clang-tidy flag")
123 return 1
125 subprocess_args = [
126 clang_tidy_path,
127 "-checks=-*,lobster-tracing",
128 ]
129 if options.compile_commands:
130 subprocess_args.append("-p")
131 subprocess_args.append(options.compile_commands)
133 subprocess_args += file_list
135 rv = subprocess.run(
136 subprocess_args,
137 stdout=subprocess.PIPE,
138 stderr=subprocess.PIPE,
139 encoding="UTF-8",
140 check=False,
141 )
143 if rv.returncode != 0:
144 print(rv.stdout)
145 print()
146 print(rv.stderr)
148 for line in rv.stdout.splitlines():
149 if "error: " in line:
150 clang_error = extract_clang_finding_name(line)
151 if clang_error and (clang_error in options.skip_clang_errors):
152 print(f"Ignoring clang-tidy error {clang_error}")
153 else:
154 print("Found clang-tidy error: ", clang_error)
155 return 1
157 db = {}
158 implementation_builder = ImplementationBuilder()
160 for line in rv.stdout.splitlines():
161 if not line.endswith("[lobster-tracing]"):
162 continue
164 match = re.match(RE_NOTAGS, line)
165 if match:
166 impl = implementation_builder.from_match(match)
167 assert impl.tag.key() not in db
168 db[impl.tag.key()] = impl
169 continue
171 match = re.match(RE_JUST, line)
172 if match:
173 impl = implementation_builder.from_match_if_new(db, match)
174 impl.just_up.append(
175 match.group(implementation_builder.REASON_GROUP_NUM),
176 )
177 continue
179 match = re.match(RE_TAGS, line)
180 if match:
181 impl = implementation_builder.from_match_if_new(db, match)
182 all_tags = match.group(implementation_builder.REFERENCE_GROUP_NUM)
183 for tag in re.split(r"[, ]+", all_tags.strip()):
184 if tag:
185 impl.add_tracing_target(
186 Tracing_Tag(
187 "req",
188 tag,
189 ),
190 )
191 continue
193 print("could not parse line")
194 print(">", line)
195 return 1
197 self._write_output(
198 schema=config.schema,
199 out_file=options.out,
200 items=db.values(),
201 )
203 return 0
206def main(args: Optional[Sequence[str]] = None) -> int:
207 return CppTool().run(args)