ACAV f0ba4b7c9529
Abstract Syntax Tree (AST) visualization tool for C, C++, and Objective-C
Loading...
Searching...
No Matches
QueryDependenciesRunner.cpp
1/*$!{
2* Aurora Clang AST Viewer (ACAV)
3*
4* Copyright (c) 2026 Min Liu
5* Copyright (c) 2026 Michael David Adams
6*
7* SPDX-License-Identifier: GPL-2.0-or-later
8*
9* This program is free software; you can redistribute it and/or modify
10* it under the terms of the GNU General Public License as published by
11* the Free Software Foundation; either version 2 of the License, or
12* (at your option) any later version.
13*
14* This program is distributed in the hope that it will be useful,
15* but WITHOUT ANY WARRANTY; without even the implied warranty of
16* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17* GNU General Public License for more details.
18*
19* You should have received a copy of the GNU General Public License along
20* with this program; if not, see <https://www.gnu.org/licenses/>.
21}$!*/
22
25#include <QCoreApplication>
26#include <QDebug>
27#include <QFile>
28#include <QJsonArray>
29#include <QJsonDocument>
30#include <QJsonParseError>
31
32namespace acav {
33
34QueryDependenciesRunner::QueryDependenciesRunner(QObject *parent)
35 : QObject(parent), process_(new QProcess(this)) {
36 connect(process_, &QProcess::finished, this,
37 &QueryDependenciesRunner::onProcessFinished);
38 connect(process_, &QProcess::errorOccurred, this,
39 &QueryDependenciesRunner::onProcessError);
40 connect(process_, &QProcess::readyReadStandardOutput, this,
41 &QueryDependenciesRunner::onProcessStdOut);
42 connect(process_, &QProcess::readyReadStandardError, this,
43 &QueryDependenciesRunner::onProcessStdErr);
44}
45
46QueryDependenciesRunner::~QueryDependenciesRunner() {
47 if (process_->state() != QProcess::NotRunning) {
48 process_->kill();
49 process_->waitForFinished();
50 }
51}
52
53void QueryDependenciesRunner::run(const QString &compilationDatabasePath,
54 const QString &outputFilePath,
55 const QString &queryDependenciesBinary) {
56 if (isRunning()) {
57 emit error("Query-dependencies is already running");
58 return;
59 }
60
61 compilationDatabasePath_ = compilationDatabasePath;
62 outputFilePath_ = outputFilePath;
63
64 // Determine the binary path
65 QString binaryPath = queryDependenciesBinary;
66 if (binaryPath.isEmpty()) {
67 // Try to find query-dependencies in the same directory as the app
68 QString appDir = QCoreApplication::applicationDirPath();
69 binaryPath = appDir + "/query-dependencies";
70 }
71
72 emit progress("Running query-dependencies...");
73 elapsed_.restart();
74
75 // Build command arguments - output to file instead of stdout
76 QStringList arguments;
77 arguments << "--compilation-database" << compilationDatabasePath;
78 arguments << "--output" << outputFilePath;
79 if (!clangResourceDir_.isEmpty()) {
80 arguments << "--clang-resource-dir" << clangResourceDir_;
81 }
82
83 qDebug() << "Running:" << binaryPath << arguments.join(" ");
84
85 process_->start(binaryPath, arguments);
86}
87
89 return process_->state() != QProcess::NotRunning;
90}
91
92void QueryDependenciesRunner::onProcessFinished(
93 int exitCode, QProcess::ExitStatus exitStatus) {
94 drainProcessOutput(process_, pendingStdout_, pendingStderr_,
95 "query-dependencies",
96 [this](const LogEntry &entry) { emit logMessage(entry); });
97
98 if (exitStatus != QProcess::NormalExit) {
99 emit error("Query-dependencies process crashed");
100 return;
101 }
102
103 if (exitCode != 0) {
104 emit error(QString("Query-dependencies failed with exit code %1")
105 .arg(exitCode));
106 return;
107 }
108
109 // Read JSON from output file
110 QFile outputFile(outputFilePath_);
111 if (!outputFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
112 emit error(QString("Failed to open dependencies file: %1")
113 .arg(outputFile.errorString()));
114 return;
115 }
116
117 QByteArray jsonData = outputFile.readAll();
118 outputFile.close();
119
120 QJsonParseError parseError;
121 QJsonDocument doc = QJsonDocument::fromJson(jsonData, &parseError);
122
123 if (parseError.error != QJsonParseError::NoError) {
124 emit error(QString("Failed to parse query-dependencies JSON output: %1")
125 .arg(parseError.errorString()));
126 return;
127 }
128
129 if (!doc.isObject()) {
130 emit error("Query-dependencies output is not a JSON object");
131 return;
132 }
133
134 QJsonObject jsonObj = doc.object();
135
136 // Check for errors section
137 if (jsonObj.contains("errors") && jsonObj["errors"].isArray()) {
138 QJsonArray errorsArray = jsonObj["errors"].toArray();
139 QStringList errorMessages;
140
141 for (const QJsonValue &errorVal : errorsArray) {
142 if (errorVal.isObject()) {
143 QJsonObject errorObj = errorVal.toObject();
144 QString path = errorObj["path"].toString();
145 QString message = errorObj["message"].toString();
146 errorMessages.append(QString("%1: %2").arg(path, message));
147 }
148 }
149
150 emit progress(QString("Dependencies loaded with %1 errors")
151 .arg(errorMessages.size()));
152 emit dependenciesReadyWithErrors(jsonObj, errorMessages);
153 } else {
154 // No errors, normal success
155 const double seconds = elapsed_.isValid()
156 ? static_cast<double>(elapsed_.elapsed()) / 1000.0
157 : 0.0;
158 emit progress(
159 QString("query-dependencies: %1s")
160 .arg(QString::number(seconds, 'f', 2)));
161 emit dependenciesReady(jsonObj);
162 }
163}
164
165void QueryDependenciesRunner::onProcessError(QProcess::ProcessError error) {
166 drainProcessOutput(process_, pendingStdout_, pendingStderr_,
167 "query-dependencies",
168 [this](const LogEntry &entry) { emit logMessage(entry); });
169
170 QString systemError = process_->errorString();
171 QString errorMessage;
172 switch (error) {
173 case QProcess::FailedToStart:
174 errorMessage = QString("Failed to start query-dependencies: %1. Make sure it's in the "
175 "same directory as the app or in PATH.").arg(systemError);
176 break;
177 case QProcess::Crashed:
178 errorMessage = QString("Query-dependencies crashed: %1").arg(systemError);
179 break;
180 case QProcess::Timedout:
181 errorMessage = QString("Query-dependencies timed out: %1").arg(systemError);
182 break;
183 case QProcess::ReadError:
184 errorMessage = QString("Error reading from query-dependencies: %1").arg(systemError);
185 break;
186 case QProcess::WriteError:
187 errorMessage = QString("Error writing to query-dependencies: %1").arg(systemError);
188 break;
189 default:
190 errorMessage = QString("Unknown error running query-dependencies: %1").arg(systemError);
191 break;
192 }
193
194 emit this->error(errorMessage);
195 emitErrorLog("query-dependencies", errorMessage,
196 [this](const LogEntry &entry) { emit logMessage(entry); });
197}
198
199void QueryDependenciesRunner::onProcessStdOut() {
200 emitParsedOutput(pendingStdout_,
201 QString::fromUtf8(process_->readAllStandardOutput()),
202 "query-dependencies", false,
203 [this](const LogEntry &entry) { emit logMessage(entry); });
204}
205
206void QueryDependenciesRunner::onProcessStdErr() {
207 emitParsedOutput(pendingStderr_,
208 QString::fromUtf8(process_->readAllStandardError()),
209 "query-dependencies", true,
210 [this](const LogEntry &entry) { emit logMessage(entry); });
211}
212
213} // namespace acav
Helpers for draining process output into log entries.
Qt runner for query-dependencies tool.
bool isRunning() const
Check if query is currently running.
void progress(const QString &message)
Emitted with progress updates (e.g., "Running query-dependencies...").
void error(const QString &errorMessage)
Emitted when an error occurs.
void run(const QString &compilationDatabasePath, const QString &outputFilePath, const QString &queryDependenciesBinary=QString())
Run query-dependencies tool with the specified compilation database.