Skip to content

Commit 009f352

Browse files
hanniavaleraHannia Valera
andauthored
Add support for custom build problem matchers in CMake Tools (#4760)
* Add support for custom build problem matchers in CMake Tools - Introduced `cmake.additionalBuildProblemMatchers` setting to allow users to define custom problem matchers for build output. - Implemented `CustomParser` to handle user-defined regex patterns and integrate diagnostics into the VS Code Problems pane. - Updated documentation and tests to cover new functionality, including examples for clang-tidy, PCLint, and cppcheck formats. * fixed regex * updated test --------- Co-authored-by: Hannia Valera <[email protected]>
1 parent 400a5f8 commit 009f352

9 files changed

Lines changed: 524 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Features:
88
- Add individual CTest test nodes to the Project Outline with inline run/debug buttons, and enable debugging tests from both the Outline and Test Explorer without requiring a launch.json. [#4721](https://github.com/microsoft/vscode-cmake-tools/pull/4721)
99
- Add "Delete Cache, Reconfigure and Build" command that chains cache deletion, reconfiguration, and build into a single action. [#4723](https://github.com/microsoft/vscode-cmake-tools/pull/4723)
1010
- Add "Set Build and Launch/Debug Target" command that sets both the build target and launch target simultaneously. [#4732](https://github.com/microsoft/vscode-cmake-tools/pull/4732)
11+
- Add `cmake.additionalBuildProblemMatchers` setting to define custom problem matchers for build output. Supports tools like clang-tidy, PCLint Plus, cppcheck, or custom scripts integrated via `add_custom_command`/`add_custom_target`. [#4077](https://github.com/microsoft/vscode-cmake-tools/issues/4077)
1112

1213
Improvements:
1314
- Make "CMake: Add ... Preset" commands available in the command palette when `cmake.useCMakePresets` is set to `auto`, even before a CMakePresets.json file exists. [#4401](https://github.com/microsoft/vscode-cmake-tools/issues/4401)

docs/cmake-settings.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ Options that support substitution, in the table below, allow variable references
4949
| `cmake.emscriptenSearchDirs` | List of paths to search for Emscripten. | `[]` | no |
5050
| `cmake.enableAutomaticKitScan` | Enable automatic kit scanning. | `true` | no |
5151
| `cmake.enabledOutputParsers` | List of enabled output parsers. | `["cmake", "gcc", "gnuld", "msvc", "ghs", "diab", "iwyu"]` | no |
52+
| `cmake.additionalBuildProblemMatchers` | Array of user-defined problem matchers for build output. Each entry has `name`, `regexp`, and optional capture group indices (`file`, `line`, `column`, `severity`, `message`, `code`). See [Additional Build Problem Matchers](#additional-build-problem-matchers) below. | `[]` | no |
5253
| `cmake.enableLanguageServices` | If `true`, enable CMake language services. | `true` | no |
5354
| `cmake.enableTraceLogging` | If `true`, enable trace logging. | `false` | no |
5455
| `cmake.environment` | An object containing `key:value` pairs of environment variables, which will be available when configuring, building, or testing with CTest. | `{}` (no environment variables) | yes |
@@ -149,6 +150,74 @@ Supported commands for substitution:
149150
|`cmake.activeBuildPresetName`|The name of the active build preset.|
150151
|`cmake.activeTestPresetName`|The name of the active test preset.|
151152

153+
## Additional build problem matchers
154+
155+
The `cmake.additionalBuildProblemMatchers` setting lets you define custom problem matchers that are applied to build output. This is useful when you integrate tools like **clang-tidy**, **PCLint Plus**, **cppcheck**, or custom scripts into your CMake build via `add_custom_command` or `add_custom_target`. Diagnostics from these tools will appear in the VS Code **Problems** pane alongside the standard compiler errors.
156+
157+
Custom matchers run **after** the built-in parsers (`gcc`, `msvc`, `gnuld`, etc.), so they will not interfere with standard compiler diagnostics.
158+
159+
Each matcher entry has the following properties:
160+
161+
| Property | Type | Required | Default | Description |
162+
|----------|------|----------|---------|-------------|
163+
| `name` | string | **yes** || Friendly name shown as the diagnostic source in the Problems pane. |
164+
| `regexp` | string | **yes** || Regular expression applied to each build output line. |
165+
| `file` | integer | no | `1` | Capture group index for the file path. |
166+
| `line` | integer | no | `2` | Capture group index for the line number. |
167+
| `column` | integer | no || Capture group index for the column number. |
168+
| `severity` | integer or string | no | `"warning"` | Either a capture group index (integer) that captures `"error"`, `"warning"`, or `"info"`, or a fixed string. |
169+
| `message` | integer | no | `3` | Capture group index for the diagnostic message. |
170+
| `code` | integer | no || Capture group index for a diagnostic code. |
171+
172+
### Examples
173+
174+
**clang-tidy** (`/path/file.cpp:10:5: warning: some message [check-name]`):
175+
176+
```json
177+
"cmake.additionalBuildProblemMatchers": [
178+
{
179+
"name": "clang-tidy",
180+
"regexp": "^(.+?):(\\d+):(\\d+):\\s+(warning|error|note):\\s+(.+?)\\s*(?:\\[(.+)\\])?$",
181+
"file": 1,
182+
"line": 2,
183+
"column": 3,
184+
"severity": 4,
185+
"message": 5,
186+
"code": 6
187+
}
188+
]
189+
```
190+
191+
**cppcheck** (`[file.cpp:10]: (warning) message`):
192+
193+
```json
194+
"cmake.additionalBuildProblemMatchers": [
195+
{
196+
"name": "cppcheck",
197+
"regexp": "^\\[(.+?):(\\d+)\\]:\\s+\\((error|warning|style|performance|portability|information)\\)\\s+(.+)$",
198+
"file": 1,
199+
"line": 2,
200+
"severity": 3,
201+
"message": 4
202+
}
203+
]
204+
```
205+
206+
**Custom script with fixed severity** (`LINT: file.cpp:7: message`):
207+
208+
```json
209+
"cmake.additionalBuildProblemMatchers": [
210+
{
211+
"name": "my-lint",
212+
"regexp": "^LINT:\\s+(.+?):(\\d+):\\s+(.+)$",
213+
"file": 1,
214+
"line": 2,
215+
"severity": "error",
216+
"message": 3
217+
}
218+
]
219+
```
220+
152221
## Next steps
153222

154223
- Learn about [user vs. workspace settings](https://code.visualstudio.com/docs/getstarted/settings)

package.json

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2574,6 +2574,69 @@
25742574
],
25752575
"scope": "resource"
25762576
},
2577+
"cmake.additionalBuildProblemMatchers": {
2578+
"type": "array",
2579+
"markdownDescription": "%cmake-tools.configuration.cmake.additionalBuildProblemMatchers.markdownDescription%",
2580+
"items": {
2581+
"type": "object",
2582+
"required": [
2583+
"name",
2584+
"regexp"
2585+
],
2586+
"properties": {
2587+
"name": {
2588+
"type": "string",
2589+
"description": "%cmake-tools.configuration.cmake.additionalBuildProblemMatchers.name%"
2590+
},
2591+
"regexp": {
2592+
"type": "string",
2593+
"description": "%cmake-tools.configuration.cmake.additionalBuildProblemMatchers.regexp%"
2594+
},
2595+
"file": {
2596+
"type": "integer",
2597+
"description": "%cmake-tools.configuration.cmake.additionalBuildProblemMatchers.file%",
2598+
"default": 1
2599+
},
2600+
"line": {
2601+
"type": "integer",
2602+
"description": "%cmake-tools.configuration.cmake.additionalBuildProblemMatchers.line%",
2603+
"default": 2
2604+
},
2605+
"column": {
2606+
"type": "integer",
2607+
"description": "%cmake-tools.configuration.cmake.additionalBuildProblemMatchers.column%"
2608+
},
2609+
"severity": {
2610+
"oneOf": [
2611+
{
2612+
"type": "integer",
2613+
"description": "%cmake-tools.configuration.cmake.additionalBuildProblemMatchers.severity.group%"
2614+
},
2615+
{
2616+
"type": "string",
2617+
"enum": [
2618+
"error",
2619+
"warning",
2620+
"info"
2621+
],
2622+
"description": "%cmake-tools.configuration.cmake.additionalBuildProblemMatchers.severity.fixed%"
2623+
}
2624+
]
2625+
},
2626+
"message": {
2627+
"type": "integer",
2628+
"description": "%cmake-tools.configuration.cmake.additionalBuildProblemMatchers.message%",
2629+
"default": 3
2630+
},
2631+
"code": {
2632+
"type": "integer",
2633+
"description": "%cmake-tools.configuration.cmake.additionalBuildProblemMatchers.code%"
2634+
}
2635+
}
2636+
},
2637+
"default": [],
2638+
"scope": "resource"
2639+
},
25772640
"cmake.debugConfig": {
25782641
"type": "object",
25792642
"description": "%cmake-tools.configuration.cmake.debugConfig.description%",

package.nls.json

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,41 @@
174174
"Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered."
175175
]
176176
},
177+
"cmake-tools.configuration.cmake.additionalBuildProblemMatchers.markdownDescription": {
178+
"message": "Additional problem matchers for build output. Use this to surface diagnostics from tools like clang-tidy, PCLint Plus, cppcheck, or custom scripts integrated via `add_custom_command`/`add_custom_target` in CMake.\n\nEach entry defines a `name` (used as the diagnostic source label), a `regexp`, and capture group indices for `file`, `line`, `column`, `severity`, `message`, and `code`.\n\nFor example, to match clang-tidy output like `/path/file.cpp:10:5: warning: some message [check-name]`:\n```json\n[\n {\n \"name\": \"clang-tidy\",\n \"regexp\": \"^(.+?):(\\\\d+):(\\\\d+):\\\\s+(warning|error|note):\\\\s+(.+?)\\\\s*(?:\\\\[(.+)\\\\])?$\",\n \"file\": 1,\n \"line\": 2,\n \"column\": 3,\n \"severity\": 4,\n \"message\": 5,\n \"code\": 6\n }\n]\n```\n\nCustom matchers run **after** the built-in parsers (`gcc`, `msvc`, etc.) so they do not steal lines from built-in compilers.",
179+
"comment": [
180+
"Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered."
181+
]
182+
},
183+
"cmake-tools.configuration.cmake.additionalBuildProblemMatchers.name": "A unique name for this matcher, used as the diagnostic source label in the Problems pane.",
184+
"cmake-tools.configuration.cmake.additionalBuildProblemMatchers.regexp": "The regular expression to match against each build output line.",
185+
"cmake-tools.configuration.cmake.additionalBuildProblemMatchers.file": {
186+
"message": "The capture group index for the file path. Defaults to `1`.",
187+
"comment": [
188+
"Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered."
189+
]
190+
},
191+
"cmake-tools.configuration.cmake.additionalBuildProblemMatchers.line": {
192+
"message": "The capture group index for the line number. Defaults to `2`.",
193+
"comment": [
194+
"Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered."
195+
]
196+
},
197+
"cmake-tools.configuration.cmake.additionalBuildProblemMatchers.column": {
198+
"message": "The capture group index for the column number. Optional.",
199+
"comment": [
200+
"Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered."
201+
]
202+
},
203+
"cmake-tools.configuration.cmake.additionalBuildProblemMatchers.severity.group": "The capture group index for the severity (should capture 'error', 'warning', or 'info').",
204+
"cmake-tools.configuration.cmake.additionalBuildProblemMatchers.severity.fixed": "A fixed severity to apply to all matches from this pattern.",
205+
"cmake-tools.configuration.cmake.additionalBuildProblemMatchers.message": {
206+
"message": "The capture group index for the diagnostic message. Defaults to `3`.",
207+
"comment": [
208+
"Markdown text between `` should not be translated or localized (they represent literal text) and the capitalization, spacing, and punctuation (including the ``) should not be altered."
209+
]
210+
},
211+
"cmake-tools.configuration.cmake.additionalBuildProblemMatchers.code": "The capture group index for an optional diagnostic code.",
177212
"cmake-tools.configuration.cmake.debugConfig.description": "The debug configuration to use when debugging a target.",
178213
"cmake-tools.configuration.cmake.debugConfig.symbolSearchPath.description": "Visual Studio debugger symbol search paths.",
179214
"cmake-tools.configuration.cmake.debugConfig.additionalSOLibSearchPath.description": "Paths for GDB or LLDB to search for .so files.",

src/config.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import * as vscode from 'vscode';
1212
import * as nls from 'vscode-nls';
1313
import { CppDebugConfiguration } from '@cmt/debug/debugger';
1414
import { Environment } from '@cmt/environmentVariables';
15+
import { BuildProblemMatcherConfig } from '@cmt/diagnostics/custom';
1516

1617
nls.config({ messageFormat: nls.MessageFormat.bundle, bundleFormat: nls.BundleFormat.standalone })();
1718
const localize: nls.LocalizeFunc = nls.loadMessageBundle();
@@ -234,6 +235,7 @@ export interface ExtensionConfigurationSettings {
234235
postRunCoverageTarget: string | null;
235236
coverageInfoFiles: string[];
236237
useFolderPropertyInBuildTargetDropdown: boolean;
238+
additionalBuildProblemMatchers: BuildProblemMatcherConfig[];
237239
}
238240

239241
type EmittersOf<T> = {
@@ -420,6 +422,9 @@ export class ConfigurationReader implements vscode.Disposable {
420422
get enableOutputParsers(): string[] | null {
421423
return this.configData.enabledOutputParsers;
422424
}
425+
get additionalBuildProblemMatchers(): BuildProblemMatcherConfig[] {
426+
return this.configData.additionalBuildProblemMatchers ?? [];
427+
}
423428
get pinnedCommands(): string[] | null {
424429
return this.configData.pinnedCommands;
425430
}
@@ -692,7 +697,8 @@ export class ConfigurationReader implements vscode.Disposable {
692697
preRunCoverageTarget: new vscode.EventEmitter<string | null>(),
693698
postRunCoverageTarget: new vscode.EventEmitter<string | null>(),
694699
coverageInfoFiles: new vscode.EventEmitter<string[]>(),
695-
useFolderPropertyInBuildTargetDropdown: new vscode.EventEmitter<boolean>()
700+
useFolderPropertyInBuildTargetDropdown: new vscode.EventEmitter<boolean>(),
701+
additionalBuildProblemMatchers: new vscode.EventEmitter<BuildProblemMatcherConfig[]>()
696702
};
697703

698704
/**

src/diagnostics/build.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import * as gnu_ld from '@cmt/diagnostics/gnu-ld';
1515
import * as mvsc from '@cmt/diagnostics/msvc';
1616
import * as iar from '@cmt/diagnostics/iar';
1717
import * as iwyu from '@cmt/diagnostics/iwyu';
18+
import { CustomParser } from '@cmt/diagnostics/custom';
1819
import { FileDiagnostic, RawDiagnostic, RawDiagnosticParser } from '@cmt/diagnostics/util';
1920
import { ConfigurationReader } from '@cmt/config';
2021
import { fs } from '@cmt/pr';
@@ -32,7 +33,15 @@ export class Compilers {
3233
}
3334

3435
export class CompileOutputConsumer implements OutputConsumer {
35-
constructor(readonly config: ConfigurationReader) {}
36+
readonly customParsers: Map<string, CustomParser> = new Map();
37+
38+
constructor(readonly config: ConfigurationReader) {
39+
for (const matcherConfig of config.additionalBuildProblemMatchers ?? []) {
40+
if (matcherConfig.name && matcherConfig.regexp) {
41+
this.customParsers.set(matcherConfig.name, new CustomParser(matcherConfig));
42+
}
43+
}
44+
}
3645

3746
compilers = new Compilers();
3847

@@ -42,9 +51,16 @@ export class CompileOutputConsumer implements OutputConsumer {
4251
}
4352

4453
error(line: string) {
54+
// Built-in parsers get first priority
4555
for (const cand in this.compilers) {
4656
if (this.compilers[cand].handleLine(line)) {
47-
break;
57+
return;
58+
}
59+
}
60+
// Custom user-defined parsers run after built-ins
61+
for (const [, parser] of this.customParsers) {
62+
if (parser.handleLine(line)) {
63+
return;
4864
}
4965
}
5066
}
@@ -128,6 +144,37 @@ export class CompileOutputConsumer implements OutputConsumer {
128144

129145
await linkerHandler.finalize(arrs);
130146

147+
// Include diagnostics from custom user-defined parsers (always enabled)
148+
for (const [name, parser] of this.customParsers) {
149+
for (const raw_diag of parser.diagnostics) {
150+
const filepath = await this.resolvePath(raw_diag.file, basePaths);
151+
const severity = severity_of(raw_diag.severity);
152+
if (severity === undefined) {
153+
continue;
154+
}
155+
156+
const diag = new vscode.Diagnostic(raw_diag.location, raw_diag.message, severity);
157+
diag.source = name;
158+
if (raw_diag.code) {
159+
diag.code = raw_diag.code;
160+
}
161+
if (!diags_by_file.has(filepath)) {
162+
diags_by_file.set(filepath, []);
163+
}
164+
diag.relatedInformation = [];
165+
for (const rel of raw_diag.related) {
166+
const relFilePath = vscode.Uri.file(await this.resolvePath(rel.file, basePaths));
167+
const related = new vscode.DiagnosticRelatedInformation(new vscode.Location(relFilePath, rel.location), rel.message);
168+
diag.relatedInformation.push(related);
169+
}
170+
diags_by_file.get(filepath)!.push(diag);
171+
arrs.push({
172+
filepath,
173+
diag
174+
});
175+
}
176+
}
177+
131178
return arrs;
132179
}
133180

0 commit comments

Comments
 (0)