Skip to content

Commit db32666

Browse files
authored
Merge pull request #1591 from theKBro/fixCmakeWarningsMultilineAndWrongDirectyIssues
Preserve CMake diagnostic messages and avoid incorrect build-directory prefixes
2 parents b59938f + 717b100 commit db32666

3 files changed

Lines changed: 186 additions & 8 deletions

File tree

‎src/main/java/edu/hm/hafner/analysis/LookaheadParser.java‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ private void parse(final Report report, final LookaheadStream lookahead) {
7070
try (var builder = new IssueBuilder()) {
7171
while (lookahead.hasNext()) {
7272
var line = lookahead.next();
73-
handleDirectoryChanges(builder, line, report);
73+
if (isDirectoryTrackingEnabled()) {
74+
handleDirectoryChanges(builder, line, report);
75+
}
7476
preprocessLine(line);
7577
if (isLineInteresting(line)) {
7678
var matcher = pattern.matcher(line);
@@ -95,6 +97,19 @@ protected void preprocessLine(final String line) {
9597
// empty default implementation does nothing
9698
}
9799

100+
/**
101+
* Returns whether build-tool directory messages should set the base directory for relative issue filenames.
102+
* Tracking is enabled by default for compiler diagnostics, including Ninja builds that use CMake's binary directory
103+
* as their working directory. The CMake marker is a directory hint, not proof of a compiler's working directory.
104+
* Parsers whose filenames are not relative to the build working directory may opt out without losing lookahead or
105+
* multiline parsing.
106+
*
107+
* @return {@code true} to track Make directory changes and CMake binary-directory markers
108+
*/
109+
protected boolean isDirectoryTrackingEnabled() {
110+
return true;
111+
}
112+
98113
/**
99114
* When changing directories using 'Entering directory' output, save new directory to our stack for later use, then
100115
* return it for use now.

‎src/main/java/edu/hm/hafner/analysis/parser/CMakeParser.java‎

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@
66
import edu.hm.hafner.analysis.Severity;
77
import edu.hm.hafner.util.LookaheadStream;
88
import java.io.Serial;
9+
import java.util.ArrayList;
10+
import java.util.List;
911
import java.util.Optional;
1012
import java.util.regex.Matcher;
1113
import org.apache.commons.lang3.StringUtils;
1214

1315
/**
14-
* A parser for CMake warnings.
16+
* A parser for CMake warnings. Keeps the source filenames reported by CMake: build-tool working directories and CMake's
17+
* binary-directory marker do not identify the source directory of a configure diagnostic. Relative filenames remain
18+
* relative for consumers to resolve using source-tree context.
1519
*
1620
* @author Uwe Brandt
1721
*/
@@ -27,24 +31,51 @@ public CMakeParser() {
2731
super(CMAKE_WARNING_PATTERN);
2832
}
2933

34+
@Override
35+
protected boolean isDirectoryTrackingEnabled() {
36+
return false;
37+
}
38+
3039
@Override
3140
protected Optional<Issue> createIssue(
3241
final Matcher matcher, final LookaheadStream lookahead, final IssueBuilder builder) {
3342
// if the category is contained in brackets, remove those brackets
3443
var category = StringUtils.strip(matcher.group("category"), "()");
35-
int prefixLength = matcher.group("prefix").length();
44+
var prefix = matcher.group("prefix");
3645
return builder.setFileName(matcher.group("file"))
3746
.setLineStart(matcher.group("line"))
3847
.setCategory(category)
39-
.setMessage(readMessage(lookahead, prefixLength))
48+
.setMessage(readMessage(lookahead, prefix))
4049
.setSeverity(Severity.guessFromString(matcher.group("type")))
4150
.buildOptional();
4251
}
4352

44-
private String readMessage(final LookaheadStream lookahead, final int prefixLength) {
45-
if (lookahead.hasNext()) {
46-
return StringUtils.substring(lookahead.next(), prefixLength).trim();
53+
private String readMessage(final LookaheadStream lookahead, final String prefix) {
54+
List<String> messageLines = new ArrayList<>();
55+
while (lookahead.hasNext()) {
56+
var line = removePrefix(lookahead.peekNext(), prefix);
57+
if (!isContinuation(line)) {
58+
break;
59+
}
60+
lookahead.next();
61+
messageLines.add(line.strip());
62+
}
63+
64+
while (!messageLines.isEmpty()
65+
&& messageLines.get(messageLines.size() - 1).isEmpty()) {
66+
messageLines.remove(messageLines.size() - 1);
67+
}
68+
return String.join("\n", messageLines);
69+
}
70+
71+
private String removePrefix(final String line, final String prefix) {
72+
if (line.startsWith(prefix)) {
73+
return line.substring(prefix.length());
4774
}
48-
return "";
75+
return line.equals(prefix.stripTrailing()) ? "" : line;
76+
}
77+
78+
private boolean isContinuation(final String line) {
79+
return line.isEmpty() || Character.isWhitespace(line.charAt(0));
4980
}
5081
}

‎src/test/java/edu/hm/hafner/analysis/parser/CMakeParserTest.java‎

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
import edu.hm.hafner.analysis.Severity;
55
import edu.hm.hafner.analysis.assertions.SoftAssertions;
66
import edu.hm.hafner.analysis.registry.AbstractParserTest;
7+
import org.junit.jupiter.api.Test;
8+
import org.junit.jupiter.params.ParameterizedTest;
9+
import org.junit.jupiter.params.provider.ValueSource;
710

811
/**
912
* Tests the class {@link CMakeParser}.
@@ -68,6 +71,135 @@ protected void assertThatIssuesArePresent(final Report report, final SoftAsserti
6871
.hasFileName("unlikely.cmake");
6972
}
7073

74+
@ParameterizedTest
75+
@ValueSource(
76+
strings = {
77+
"-- Build files have been written to: C:/workspace/build",
78+
"22>-- Build files have been written to: C:/workspace/build",
79+
"[timestamp] C/C++: -- Build files have been written to: C:/workspace/build",
80+
"make[1]: Entering directory 'C:/workspace/build'",
81+
"make[1]: Leaving directory 'C:/workspace/build'",
82+
"-- Build files have an unrelated message",
83+
"Entering directory without a valid path"
84+
})
85+
void shouldIgnoreBuildDirectoryMessages(final String directoryMessage) {
86+
var report = parseStringContent(directoryMessage + """
87+
88+
CMake Warning at subproject/CMakeLists.txt:14 (message):
89+
Configure warning
90+
""");
91+
92+
try (var softly = new SoftAssertions()) {
93+
softly.assertThat(report).hasSize(1).doesNotHaveErrors();
94+
softly.assertThat(report.get(0))
95+
.hasFileName("subproject/CMakeLists.txt")
96+
.hasPath("-")
97+
.hasLineStart(14)
98+
.hasMessage("Configure warning");
99+
}
100+
}
101+
102+
@Test
103+
void shouldPreserveAbsoluteSourcePaths() {
104+
var report = parseStringContent("""
105+
-- Build files have been written to: C:/workspace/build
106+
CMake Warning at C:/workspace/source/cmake/options.cmake:7 (message):
107+
Warning with an absolute source path
108+
""");
109+
110+
try (var softly = new SoftAssertions()) {
111+
softly.assertThat(report).hasSize(1).doesNotHaveErrors();
112+
softly.assertThat(report.get(0))
113+
.hasFileName("C:/workspace/source/cmake/options.cmake")
114+
.hasLineStart(7)
115+
.hasMessage("Warning with an absolute source path");
116+
}
117+
}
118+
119+
@Test
120+
void shouldNotCarryDirectoriesBetweenProjects() {
121+
var report = parseStringContent("""
122+
make: Entering directory 'C:/workspace/build'
123+
-- Build files have been written to: C:/workspace/build/external
124+
CMake Warning at external/CMakeLists.txt:3 (message):
125+
External warning
126+
make: Leaving directory 'C:/workspace/build'
127+
-- Build files have been written to: C:/workspace/build/root
128+
CMake Warning at CMakeLists.txt:9 (message):
129+
Root warning
130+
""");
131+
132+
try (var softly = new SoftAssertions()) {
133+
softly.assertThat(report).hasSize(2).doesNotHaveErrors();
134+
softly.assertThat(report.get(0))
135+
.hasFileName("external/CMakeLists.txt")
136+
.hasMessage("External warning");
137+
softly.assertThat(report.get(1)).hasFileName("CMakeLists.txt").hasMessage("Root warning");
138+
}
139+
}
140+
141+
@Test
142+
void shouldReadPrefixedMultilineMessages() {
143+
var report = parseStringContent("""
144+
[step] CMake Warning at CMakeLists.txt:9 (message):
145+
[step] First line
146+
[step]
147+
[step] A wrapped explanation, continued
148+
[step] on another line.
149+
[step]
150+
[step] CMake Warning at nested/CMakeLists.txt:4 (message):
151+
[step] Next warning
152+
""");
153+
154+
try (var softly = new SoftAssertions()) {
155+
softly.assertThat(report).hasSize(2).doesNotHaveErrors();
156+
softly.assertThat(report.get(0)).hasFileName("CMakeLists.txt").hasMessage("""
157+
First line
158+
159+
A wrapped explanation, continued
160+
on another line.""");
161+
softly.assertThat(report.get(1))
162+
.hasFileName("nested/CMakeLists.txt")
163+
.hasMessage("Next warning");
164+
}
165+
}
166+
167+
@Test
168+
void shouldExcludeCallStackFromMessage() {
169+
var report = parseStringContent("""
170+
CMake Warning at CMakeLists.txt:14 (message):
171+
Warning raised by the nested project
172+
Call Stack (most recent call first):
173+
external-project/CMakeLists.txt:4 (include)
174+
CMakeLists.txt:38 (ExternalProject_Add)
175+
CMake Warning at after.cmake:3 (message):
176+
Warning after the call stack
177+
""");
178+
179+
try (var softly = new SoftAssertions()) {
180+
softly.assertThat(report).hasSize(2).doesNotHaveErrors();
181+
softly.assertThat(report.get(0))
182+
.hasFileName("CMakeLists.txt")
183+
.hasMessage("Warning raised by the nested project");
184+
softly.assertThat(report.get(1)).hasFileName("after.cmake").hasMessage("Warning after the call stack");
185+
}
186+
}
187+
188+
@Test
189+
void shouldNotConsumeWarningAfterEmptyMessage() {
190+
var report = parseStringContent("""
191+
CMake Warning at empty.cmake:3 (message):
192+
CMake Warning at after.cmake:4 (message):
193+
Next warning
194+
""");
195+
196+
try (var softly = new SoftAssertions()) {
197+
softly.assertThat(report).hasSize(2).doesNotHaveErrors();
198+
softly.assertThat(report.get(0)).hasFileName("empty.cmake").hasMessage("");
199+
softly.assertThat(report.get(1)).hasFileName("after.cmake").hasMessage("Next warning");
200+
}
201+
}
202+
71203
@Override
72204
protected CMakeParser createParser() {
73205
return new CMakeParser();

0 commit comments

Comments
 (0)