-
Notifications
You must be signed in to change notification settings - Fork 429
/
cmake.ts
182 lines (173 loc) · 5.72 KB
/
cmake.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
/**
* Parsing of CMake configure diagnostics
*/ /** */
import {Logger} from '@cmt/logging';
import {OutputConsumer} from '@cmt/proc';
import * as util from '@cmt/util';
import * as vscode from 'vscode';
import {FileDiagnostic, oneLess} from './util';
/**
* Class which consumes output from CMake.
*
* This class is in charge of logging CMake's output, as well as parsing and
* collecting warnings and errors from the configure step. It should be used
* in conjunction with `proc.execute`.
*/
export class CMakeOutputConsumer implements OutputConsumer {
constructor(readonly sourceDir: string, readonly logger?: Logger) {}
/**
* The diagnostics that this consumer has accumulated. It will be populated
* during calls to `output()` and `error()`
*/
get diagnostics() { return this._diagnostics; }
private readonly _diagnostics = [] as FileDiagnostic[];
/**
* Simply writes the line of output to the log
* @param line Line of output
*/
output(line: string) {
if (this.logger) {
this.logger.info(line);
}
this._parseDiags(line);
}
/**
* The state for the diagnostic parser. Implemented as a crude FSM
*/
private readonly _errorState: {
/**
* The state of the parser. `init` is the rest state. `diag` is the state
* of active parsing. `stack` is parsing the CMake call stack from an error
* or warning.
*/
state: ('init'|'diag'|'stack'),
/**
* The diagnostic that is currently being accumulated into
*/
diag: FileDiagnostic|null,
/**
* The number of blank lines encountered thus far. CMake signals the end of
* a warning or error with blank lines
*/
blankLines: number,
}
= {
state: 'init',
diag: null,
blankLines: 0,
};
/**
* Consume a line of stderr.
* @param line The line from stderr
*/
error(line: string) {
// First, just log the line
if (this.logger) {
this.logger.error(line);
}
this._parseDiags(line);
}
private _parseDiags(line: string) {
// This line of output terminates an `AUTHOR_WARNING`
const dev_warning_re = /^This warning is for project developers\./;
// Switch on the state to implement our crude FSM
switch (this._errorState.state) {
case 'init': {
const re = /CMake (.*?)(?: \(dev\))? at (.*?):(\d+) \((.*?)\):/;
const result = re.exec(line);
if (result) {
// We have encountered and error
const [_full, level, filename, linestr, command] = result;
// tslint:disable-next-line
_full; // unused
const lineno = oneLess(linestr);
const diagmap: {[k: string]: vscode.DiagnosticSeverity} = {
Warning: vscode.DiagnosticSeverity.Warning,
Error: vscode.DiagnosticSeverity.Error,
};
const vsdiag = new vscode.Diagnostic(new vscode.Range(lineno, 0, lineno, 9999), '', diagmap[level]);
vsdiag.source = `CMake (${command})`;
vsdiag.relatedInformation = [];
const filepath = util.resolvePath(filename, this.sourceDir);
this._errorState.diag = {
filepath,
diag: vsdiag,
};
this._errorState.state = 'diag';
this._errorState.blankLines = 0;
}
break;
}
case 'diag': {
console.assert(this._errorState.diag, 'No diagnostic?');
const call_stack_re = /^Call Stack \(most recent call first\):$/;
if (call_stack_re.test(line)) {
// We're in call stack mode!
this._errorState.state = 'stack';
this._errorState.blankLines = 0;
break;
}
if (line == '') {
// A blank line!
if (this._errorState.blankLines == 0) {
// First blank. Okay
this._errorState.blankLines++;
this._errorState.diag!.diag.message += '\n';
} else {
// Second blank line. Now we commit the diagnostic.
this._commitDiag();
}
} else if (dev_warning_re.test(line)) {
this._commitDiag();
} else {
// Reset blank line count
this._errorState.blankLines = 0;
// Add this line to the current diag accumulator
const trimmed = line.replace(/^ /, '');
this._errorState.diag!.diag.message += trimmed + '\n';
}
break;
}
case 'stack': {
// Meh... vscode doesn't really let us provide call stacks to diagnostics.
// We can't really do anything...
if (line.trim() == '') {
if (this._errorState.blankLines == 1) {
this._commitDiag();
} else {
this._errorState.blankLines++;
}
} else if (dev_warning_re.test(line)) {
this._commitDiag();
} else {
const stackElemRe = /^ (.*):(\d+) \((\w+)\)$/;
const mat = stackElemRe.exec(line);
if (mat) {
const [, filepath, lineNoStr, command] = mat;
const fileUri = vscode.Uri.file(util.resolvePath(filepath, this.sourceDir));
const lineNo = parseInt(lineNoStr) - 1;
const related = new vscode.DiagnosticRelatedInformation(
new vscode.Location(fileUri, new vscode.Range(lineNo, 0, lineNo, 999)),
`In call to '${command}' here`,
);
console.assert(this._errorState.diag);
this._errorState.diag!.diag.relatedInformation!.push(related);
}
}
break;
}
}
}
/**
* Commit the accumulated diagnostic and go back to `init` state.
*/
private _commitDiag() {
const diag = this._errorState.diag!;
// Remove the final newline(s) from the message, for prettiness
diag.diag.message = diag.diag.message.replace(/\n+$/, '');
this._diagnostics.push(this._errorState.diag!);
this._errorState.diag = null;
this._errorState.blankLines = 0;
this._errorState.state = 'init';
}
}