1// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd.
2//
3// SPDX-License-Identifier: GPL-3.0-or-later
4
5#include "gnumakeparser.h"
6
7#include "services/builder/task.h"
8#include "services/builder/fileutils.h"
9
10#include "common/util/qtcassert.h"
11
12#include <QDir>
13#include <QFile>
14
15const char TASK_CATEGORY_BUILDSYSTEM[] = "Task.Category.Buildsystem";
16
17namespace {
18 // optional full path, make executable name, optional exe extension, optional number in square brackets, colon space
19 const char * const MAKEEXEC_PATTERN("^(.*?[/\\\\])?(mingw(32|64)-|g)?make(.exe)?(\\[\\d+\\])?:\\s");
20 const char * const MAKEFILE_PATTERN("^((.*?[/\\\\])?[Mm]akefile(\\.[a-zA-Z]+)?):(\\d+):\\s");
21}
22
23GnuMakeParser::GnuMakeParser()
24{
25 setObjectName(QLatin1String("GnuMakeParser"));
26 makeDir.setPattern(QLatin1String(MAKEEXEC_PATTERN) +
27 QLatin1String("(\\w+) directory .(.+).$"));
28 QTC_CHECK(makeDir.isValid());
29 makeLine.setPattern(QLatin1String(MAKEEXEC_PATTERN) + QLatin1String("(.*)$"));
30 QTC_CHECK(makeLine.isValid());
31 errorInMakefile.setPattern(QLatin1String(MAKEFILE_PATTERN) + QLatin1String("(.*)$"));
32 QTC_CHECK(errorInMakefile.isValid());
33}
34
35void GnuMakeParser::setWorkingDirectory(const QString &workingDirectory)
36{
37 addDirectory(workingDirectory);
38 IOutputParser::setWorkingDirectory(workingDirectory);
39}
40
41bool GnuMakeParser::hasFatalErrors() const
42{
43 return (fatalErrorCount > 0) || IOutputParser::hasFatalErrors();
44}
45
46void GnuMakeParser::stdOutput(const QString &line, OutputPane::OutputFormat format)
47{
48 const QString lne = rightTrimmed(line);
49
50 QRegularExpressionMatch match = makeDir.match(lne);
51 if (match.hasMatch()) {
52 if (match.captured(6) == QLatin1String("Leaving"))
53 removeDirectory(match.captured(7));
54 else
55 addDirectory(match.captured(7));
56 return;
57 }
58
59 IOutputParser::stdOutput(line, format);
60}
61
62class Result {
63public:
64 Result() = default;
65
66 QString description;
67 bool isFatal = false;
68 Task::TaskType type = Task::Error;
69};
70
71static Result parseDescription(const QString &description)
72{
73 Result result;
74 if (description.startsWith(QLatin1String("warning: "), Qt::CaseInsensitive)) {
75 result.description = description.mid(9);
76 result.type = Task::Warning;
77 result.isFatal = false;
78 } else if (description.startsWith(QLatin1String("*** "))) {
79 result.description = description.mid(4);
80 result.type = Task::Error;
81 result.isFatal = true;
82 } else {
83 result.description = description;
84 result.type = Task::Error;
85 result.isFatal = false;
86 }
87 return result;
88}
89
90void GnuMakeParser::stdError(const QString &line)
91{
92 const QString lne = rightTrimmed(line);
93
94 QRegularExpressionMatch match = errorInMakefile.match(lne);
95 if (match.hasMatch()) {
96 flush();
97 Result res = parseDescription(match.captured(5));
98 if (res.isFatal)
99 ++fatalErrorCount;
100 if (!suppressIssues) {
101 taskAdded(Task(res.type, res.description,
102 Utils::FileName::fromUserInput(match.captured(1)) /* filename */,
103 match.captured(4).toInt(), /* line */
104 TASK_CATEGORY_BUILDSYSTEM), 1, 0);
105 }
106 return;
107 }
108 match = makeLine.match(lne);
109 if (match.hasMatch()) {
110 flush();
111 Result res = parseDescription(match.captured(6));
112 if (res.isFatal)
113 ++fatalErrorCount;
114 if (!suppressIssues) {
115 Task task = Task(res.type, res.description,
116 Utils::FileName() /* filename */, -1, /* line */
117 TASK_CATEGORY_BUILDSYSTEM);
118 taskAdded(task, 1, 0);
119 }
120 return;
121 }
122
123 IOutputParser::stdError(line);
124}
125
126void GnuMakeParser::addDirectory(const QString &dir)
127{
128 if (dir.isEmpty())
129 return;
130 directories.append(dir);
131}
132
133void GnuMakeParser::removeDirectory(const QString &dir)
134{
135 directories.removeOne(dir);
136}
137
138void GnuMakeParser::taskAdded(const Task &task, int linkedLines, int skippedLines)
139{
140 Task editable(task);
141
142 if (task.type == Task::Error) {
143 // assume that all make errors will be follow up errors:
144 suppressIssues = true;
145 }
146
147 QString filePath(task.file.toString());
148
149 if (!filePath.isEmpty() && !QDir::isAbsolutePath(filePath)) {
150 QList<QFileInfo> possibleFiles;
151 foreach (const QString &dir, directories) {
152 QFileInfo candidate(dir + QLatin1Char('/') + filePath);
153 if (candidate.exists()
154 && !possibleFiles.contains(candidate)) {
155 possibleFiles << candidate;
156 }
157 }
158 if (possibleFiles.size() == 1)
159 editable.file = Utils::FileName(possibleFiles.first());
160 // Let the Makestep apply additional heuristics (based on
161 // files in ther project) if we cannot uniquely
162 // identify the file!
163 }
164
165 IOutputParser::taskAdded(editable, linkedLines, skippedLines);
166}
167