ACAV f0ba4b7c9529
Abstract Syntax Tree (AST) visualization tool for C, C++, and Objective-C
Loading...
Searching...
No Matches
QueryDependenciesParallelRunner.cpp
Go to the documentation of this file.
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
26#include "common/ClangUtils.h"
27#include <QCoreApplication>
28#include <QDebug>
29#include <QFile>
30#include <QJsonArray>
31#include <QJsonDocument>
32#include <QJsonParseError>
33
34namespace acav {
35
36QueryDependenciesParallelRunner::QueryDependenciesParallelRunner(QObject *parent)
37 : ParallelProcessRunner(parent) {}
38
40 const QString &compilationDatabasePath,
41 const QString &outputFilePath,
42 const QString &queryDependenciesBinary) {
43
44 compilationDatabasePath_ = compilationDatabasePath;
45
46 // Determine binary path
47 queryDependenciesBinary_ = queryDependenciesBinary.isEmpty()
48 ? QCoreApplication::applicationDirPath() + "/query-dependencies"
49 : queryDependenciesBinary;
50
51 // Extract source files from compilation database
52 std::string errorMsg;
53 std::vector<std::string> sourceFilesVec =
55 compilationDatabasePath.toStdString(), errorMsg);
56
57 if (sourceFilesVec.empty()) {
58 emit error(QString("Failed to load source files: %1")
59 .arg(QString::fromStdString(errorMsg)));
60 return;
61 }
62
63 // Convert to QStringList
64 QStringList sourceFiles;
65 for (const auto& file : sourceFilesVec) {
66 sourceFiles.append(QString::fromStdString(file));
67 }
68
69 // Run parallel execution (calls base class)
70 elapsed_.restart();
71 runParallel(queryDependenciesBinary_, sourceFiles, outputFilePath);
72}
73
75 int chunkIndex,
76 const QStringList &chunkData,
77 const QString &tempOutputPath) {
78
79 Q_UNUSED(chunkIndex); // Not used in current implementation
80
81 QStringList arguments;
82 arguments << "--compilation-database" << compilationDatabasePath_;
83 arguments << "--output" << tempOutputPath;
84 if (!clangResourceDir_.isEmpty()) {
85 arguments << "--clang-resource-dir" << clangResourceDir_;
86 }
87
88 // Add source files for this chunk
89 for (const QString& sourceFile : chunkData) {
90 arguments << "--source" << sourceFile;
91 }
92
93 return arguments;
94}
95
97 const QStringList &tempOutputPaths,
98 const QString &finalOutputPath,
99 QString &errorMessage) {
100
101 // Aggregate data
102 QJsonArray mergedFiles;
103 QJsonArray mergedErrors;
104 int totalCount = 0;
105 int successCount = 0;
106 int failureCount = 0;
107 int totalHeaderCount = 0;
108
109 // Load and merge each chunk
110 for (const QString& tempPath : tempOutputPaths) {
111 QFile file(tempPath);
112 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
113 qDebug() << "Warning: Could not open chunk file:" << tempPath;
114 continue;
115 }
116
117 QByteArray jsonData = file.readAll();
118 file.close();
119
120 QJsonParseError parseError;
121 QJsonDocument doc = QJsonDocument::fromJson(jsonData, &parseError);
122 if (parseError.error != QJsonParseError::NoError) {
123 qDebug() << "Warning: Could not parse chunk file:" << tempPath
124 << "Error:" << parseError.errorString();
125 continue;
126 }
127
128 QJsonObject chunkObj = doc.object();
129
130 // Merge statistics
131 if (chunkObj.contains("statistics")) {
132 QJsonObject stats = chunkObj["statistics"].toObject();
133 totalCount += stats["totalCount"].toInt();
134 successCount += stats["successCount"].toInt();
135 failureCount += stats["failureCount"].toInt();
136 totalHeaderCount += stats["totalHeaderCount"].toInt();
137 }
138
139 // Merge files array
140 if (chunkObj.contains("files")) {
141 QJsonArray filesArray = chunkObj["files"].toArray();
142 for (const QJsonValue& val : filesArray) {
143 mergedFiles.append(val);
144 }
145 }
146
147 // Merge errors array
148 if (chunkObj.contains("errors")) {
149 QJsonArray errorsArray = chunkObj["errors"].toArray();
150 for (const QJsonValue& val : errorsArray) {
151 mergedErrors.append(val);
152 }
153 }
154 }
155
156 // Build final JSON
157 QJsonObject finalObj;
158 finalObj["statistics"] = QJsonObject{
159 {"totalCount", totalCount},
160 {"successCount", successCount},
161 {"failureCount", failureCount},
162 {"totalHeaderCount", totalHeaderCount}
163 };
164 finalObj["files"] = mergedFiles;
165
166 if (!mergedErrors.isEmpty()) {
167 finalObj["errors"] = mergedErrors;
168 }
169
170 // Write to final output file
171 QFile outputFile(finalOutputPath);
172 if (!outputFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
173 errorMessage = QString("Cannot open output file: %1")
174 .arg(outputFile.errorString());
175 return false;
176 }
177
178 QJsonDocument finalDoc(finalObj);
179 outputFile.write(finalDoc.toJson(QJsonDocument::Indented));
180 outputFile.close();
181
182 return true;
183}
184
186 int successCount, int failureCount, int totalCount) {
187
188 // Call base class implementation (does merging)
189 ParallelProcessRunner::onAllCompleted(successCount, failureCount, totalCount);
190
191 // If merge failed, base class already emitted error
192 // Check if final output exists
193 QFile outputFile(finalOutputPath_);
194 if (!outputFile.exists()) {
195 return; // Error already emitted
196 }
197
198 // Read and parse final merged JSON
199 if (!outputFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
200 emit error(QString("Failed to read merged output: %1")
201 .arg(outputFile.errorString()));
202 return;
203 }
204
205 QByteArray jsonData = outputFile.readAll();
206 outputFile.close();
207
208 QJsonParseError parseError;
209 QJsonDocument doc = QJsonDocument::fromJson(jsonData, &parseError);
210 if (parseError.error != QJsonParseError::NoError) {
211 emit error(QString("Failed to parse merged JSON: %1")
212 .arg(parseError.errorString()));
213 return;
214 }
215
216 QJsonObject jsonObj = doc.object();
217
218 // Emit domain-specific signals
219 if (!errorMessages_.empty() ||
220 (jsonObj.contains("errors") && !jsonObj["errors"].toArray().isEmpty())) {
222 } else {
223 const double seconds = elapsed_.isValid()
224 ? static_cast<double>(elapsed_.elapsed()) / 1000.0
225 : 0.0;
226 emit progress(
227 QString("query-dependencies (parallel): %1s")
228 .arg(QString::number(seconds, 'f', 2)));
229 emit dependenciesReady(jsonObj);
230 }
231}
232
233} // namespace acav
Utilities for interacting with Clang at runtime This includes runtime detection of Clang paths and AS...
std::vector< std::string > getSourceFilesFromCompilationDatabase(const std::string &compDbPath, std::string &errorMessage)
Extract source file paths from a compilation database.
Parallel runner specifically for query-dependencies tool.
Generic parallel process runner for executing multiple instances of a program.
void error(const QString &errorMessage)
Emitted when an error occurs that prevents execution.
void progress(const QString &message)
Emitted with progress updates (e.g., "Completed 3/8 chunks").
QString finalOutputPath_
Final output file path.
virtual void onAllCompleted(int successCount, int failureCount, int totalCount)
Called when all processes have completed (success or failure).
QStringList errorMessages_
Aggregated error messages.
void runParallel(const QString &programPath, const QStringList &inputData, const QString &finalOutputPath)
Start parallel execution.
bool mergeResults(const QStringList &tempOutputPaths, const QString &finalOutputPath, QString &errorMessage) override
Merge JSON dependency files.
void dependenciesReadyWithErrors(const QJsonObject &dependencies, const QStringList &errorMessages)
Emitted when processes complete with some errors.
void run(const QString &compilationDatabasePath, const QString &outputFilePath, const QString &queryDependenciesBinary=QString())
Run query-dependencies in parallel.
void dependenciesReady(const QJsonObject &dependencies)
Emitted when all processes complete successfully.
QStringList prepareProcessArguments(int chunkIndex, const QStringList &chunkData, const QString &tempOutputPath) override
Build command-line arguments for query-dependencies.
void onAllCompleted(int successCount, int failureCount, int totalCount) override
Handle completion and emit domain-specific signals.