mirror of
https://github.com/Palm1r/QodeAssist.git
synced 2026-07-20 10:10:56 -04:00
refactor: Restructure project into sources/tests layout and dissolve PluginLLMCore
This commit is contained in:
305
sources/tools/BuildProjectTool.cpp
Normal file
305
sources/tools/BuildProjectTool.cpp
Normal file
@@ -0,0 +1,305 @@
|
||||
// Copyright (C) 2025-2026 Petr Mironychev
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// Additional attribution terms under GPLv3 §7(b) apply — see LICENSE
|
||||
|
||||
#include "BuildProjectTool.hpp"
|
||||
|
||||
#include "GetIssuesListTool.hpp"
|
||||
|
||||
#include "plugin/Version.hpp"
|
||||
#include <logger/Logger.hpp>
|
||||
#include <projectexplorer/buildmanager.h>
|
||||
#include <projectexplorer/project.h>
|
||||
#include <projectexplorer/projectexplorer.h>
|
||||
#include <projectexplorer/projectexplorerconstants.h>
|
||||
#include <projectexplorer/projectmanager.h>
|
||||
#include <projectexplorer/runconfiguration.h>
|
||||
#include <projectexplorer/target.h>
|
||||
#include <projectexplorer/task.h>
|
||||
#include <utils/id.h>
|
||||
#include <QApplication>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QMetaObject>
|
||||
#include <QTimer>
|
||||
|
||||
namespace QodeAssist::Tools {
|
||||
|
||||
BuildProjectTool::BuildProjectTool(QObject *parent)
|
||||
: BaseTool(parent)
|
||||
{
|
||||
}
|
||||
|
||||
BuildProjectTool::~BuildProjectTool()
|
||||
{
|
||||
for (auto it = m_activeBuilds.begin(); it != m_activeBuilds.end(); ++it) {
|
||||
BuildInfo &info = it.value();
|
||||
if (info.buildFinishedConnection) {
|
||||
disconnect(info.buildFinishedConnection);
|
||||
}
|
||||
if (info.promise) {
|
||||
info.promise->finish();
|
||||
}
|
||||
}
|
||||
m_activeBuilds.clear();
|
||||
}
|
||||
|
||||
QString BuildProjectTool::id() const
|
||||
{
|
||||
return "build_project";
|
||||
}
|
||||
|
||||
QString BuildProjectTool::displayName() const
|
||||
{
|
||||
return "Building and running project";
|
||||
}
|
||||
|
||||
QString BuildProjectTool::description() const
|
||||
{
|
||||
return "Build the active Qt Creator project using its current build configuration and block "
|
||||
"until the build finishes. Returns success/failure along with the full compiler output "
|
||||
"(stdout + stderr). Use `get_issues_list` afterwards to get structured diagnostics. "
|
||||
"This call is blocking and may take a long time for large projects.";
|
||||
}
|
||||
|
||||
QJsonObject BuildProjectTool::parametersSchema() const
|
||||
{
|
||||
QJsonObject definition;
|
||||
definition["type"] = "object";
|
||||
|
||||
QJsonObject properties;
|
||||
properties["rebuild"] = QJsonObject{
|
||||
{"type", "boolean"},
|
||||
{"description", "Force a clean rebuild instead of incremental build (default: false)"}};
|
||||
properties["run_after_build"] = QJsonObject{
|
||||
{"type", "boolean"},
|
||||
{"description", "Run the project after successful build (default: false)"}};
|
||||
|
||||
definition["properties"] = properties;
|
||||
definition["required"] = QJsonArray();
|
||||
|
||||
return definition;
|
||||
}
|
||||
|
||||
QFuture<LLMQore::ToolResult> BuildProjectTool::executeAsync(const QJsonObject &input)
|
||||
{
|
||||
auto *project = ProjectExplorer::ProjectManager::startupProject();
|
||||
if (!project) {
|
||||
return QtFuture::makeReadyFuture(
|
||||
LLMQore::ToolResult::error("Error: No active project found. Please open a project in Qt Creator."));
|
||||
}
|
||||
|
||||
if (ProjectExplorer::BuildManager::isBuilding(project)) {
|
||||
return QtFuture::makeReadyFuture(
|
||||
LLMQore::ToolResult::error("Error: Build is already in progress. Please wait for it to complete."));
|
||||
}
|
||||
|
||||
if (m_activeBuilds.contains(project)) {
|
||||
return QtFuture::makeReadyFuture(
|
||||
LLMQore::ToolResult::error(QString("Error: Build is already being tracked for project '%1'.")
|
||||
.arg(project->displayName())));
|
||||
}
|
||||
|
||||
bool rebuild = input.value("rebuild").toBool(false);
|
||||
bool runAfterBuild = input.value("run_after_build").toBool(false);
|
||||
|
||||
LOG_MESSAGE(QString("BuildProjectTool: %1 project '%2'%3")
|
||||
.arg(rebuild ? QString("Rebuilding") : QString("Building"))
|
||||
.arg(project->displayName())
|
||||
.arg(runAfterBuild ? QString(" (run after build)") : QString()));
|
||||
|
||||
auto promise = QSharedPointer<QPromise<LLMQore::ToolResult>>::create();
|
||||
promise->start();
|
||||
|
||||
BuildInfo buildInfo;
|
||||
buildInfo.promise = promise;
|
||||
buildInfo.project = project;
|
||||
buildInfo.projectName = project->displayName();
|
||||
buildInfo.isRebuild = rebuild;
|
||||
buildInfo.runAfterBuild = runAfterBuild;
|
||||
|
||||
auto *buildManager = ProjectExplorer::BuildManager::instance();
|
||||
buildInfo.buildFinishedConnection = QObject::connect(
|
||||
buildManager,
|
||||
&ProjectExplorer::BuildManager::buildQueueFinished,
|
||||
this,
|
||||
&BuildProjectTool::onBuildQueueFinished);
|
||||
|
||||
m_activeBuilds.insert(project, buildInfo);
|
||||
|
||||
QMetaObject::invokeMethod(
|
||||
qApp,
|
||||
[project, rebuild]() {
|
||||
if (rebuild) {
|
||||
ProjectExplorer::BuildManager::rebuildProjectWithDependencies(
|
||||
project, ProjectExplorer::ConfigSelection::Active);
|
||||
} else {
|
||||
ProjectExplorer::BuildManager::buildProjectWithDependencies(
|
||||
project, ProjectExplorer::ConfigSelection::Active);
|
||||
}
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
|
||||
return promise->future();
|
||||
}
|
||||
|
||||
void BuildProjectTool::onBuildQueueFinished(bool success)
|
||||
{
|
||||
QList<ProjectExplorer::Project *> projectsToCleanup;
|
||||
|
||||
for (auto it = m_activeBuilds.begin(); it != m_activeBuilds.end(); ++it) {
|
||||
ProjectExplorer::Project *project = it.key();
|
||||
|
||||
if (!ProjectExplorer::BuildManager::isBuilding(project)) {
|
||||
BuildInfo &info = it.value();
|
||||
|
||||
if (info.promise && info.promise->future().isCanceled()) {
|
||||
LOG_MESSAGE(QString("BuildProjectTool: Build cancelled for project '%1'")
|
||||
.arg(info.projectName));
|
||||
projectsToCleanup.append(project);
|
||||
continue;
|
||||
}
|
||||
|
||||
QString result = collectBuildResults(success, info.projectName, info.isRebuild);
|
||||
|
||||
if (success && info.runAfterBuild) {
|
||||
scheduleProjectRun(project, info.projectName, result);
|
||||
} else if (!success && info.runAfterBuild) {
|
||||
result += QString("\n\nProject was not started due to build failure.");
|
||||
}
|
||||
|
||||
if (info.promise) {
|
||||
info.promise->addResult(LLMQore::ToolResult::text(result));
|
||||
info.promise->finish();
|
||||
}
|
||||
|
||||
projectsToCleanup.append(project);
|
||||
}
|
||||
}
|
||||
|
||||
for (ProjectExplorer::Project *project : projectsToCleanup) {
|
||||
cleanupBuildInfo(project);
|
||||
}
|
||||
}
|
||||
|
||||
void BuildProjectTool::scheduleProjectRun(ProjectExplorer::Project *project,
|
||||
const QString &projectName,
|
||||
QString &result)
|
||||
{
|
||||
auto *target = project->activeTarget();
|
||||
if (!target) {
|
||||
result += QString("\n\nError: No active target found for the project.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto *runConfig = target->activeRunConfiguration();
|
||||
if (!runConfig) {
|
||||
result += QString("\n\nError: No active run configuration found for the project.");
|
||||
return;
|
||||
}
|
||||
|
||||
QString runConfigName = runConfig->displayName();
|
||||
result += QString("\n\nProject '%1' will be started with run configuration '%2'.")
|
||||
.arg(projectName, runConfigName);
|
||||
|
||||
ProjectExplorer::ProjectExplorerPlugin::runProject(project, Utils::Id(ProjectExplorer::Constants::NORMAL_RUN_MODE));
|
||||
}
|
||||
|
||||
QString BuildProjectTool::collectBuildResults(
|
||||
bool success, const QString &projectName, bool isRebuild)
|
||||
{
|
||||
QStringList results;
|
||||
|
||||
// Build header
|
||||
QString buildType = isRebuild ? QString("Rebuild") : QString("Build");
|
||||
QString statusText = success ? QString("✓ SUCCEEDED") : QString("✗ FAILED");
|
||||
|
||||
results.append(QString("%1 %2 for project '%3'\n")
|
||||
.arg(buildType, statusText, projectName));
|
||||
|
||||
const auto tasks = IssuesTracker::instance().getTasks();
|
||||
|
||||
if (!tasks.isEmpty()) {
|
||||
int errorCount = 0;
|
||||
int warningCount = 0;
|
||||
QStringList issuesList;
|
||||
|
||||
for (const ProjectExplorer::Task &task : tasks) {
|
||||
#if QODEASSIST_QT_CREATOR_VERSION >= QT_VERSION_CHECK(18, 0, 0)
|
||||
auto taskType = task.type();
|
||||
auto taskFile = task.file();
|
||||
auto taskLine = task.line();
|
||||
auto taskColumn = task.column();
|
||||
#else
|
||||
auto taskType = task.type;
|
||||
auto taskFile = task.file;
|
||||
auto taskLine = task.line;
|
||||
auto taskColumn = task.column;
|
||||
#endif
|
||||
|
||||
QString typeStr;
|
||||
switch (taskType) {
|
||||
case ProjectExplorer::Task::Error:
|
||||
typeStr = QString("ERROR");
|
||||
errorCount++;
|
||||
break;
|
||||
case ProjectExplorer::Task::Warning:
|
||||
typeStr = QString("WARNING");
|
||||
warningCount++;
|
||||
break;
|
||||
default:
|
||||
continue;
|
||||
}
|
||||
|
||||
if (issuesList.size() < 50) {
|
||||
QString issueText = QString("[%1] %2").arg(typeStr, task.description());
|
||||
|
||||
if (!taskFile.isEmpty()) {
|
||||
issueText += QString("\n File: %1").arg(taskFile.toUrlishString());
|
||||
if (taskLine > 0) {
|
||||
issueText += QString(":%1").arg(taskLine);
|
||||
if (taskColumn > 0) {
|
||||
issueText += QString(":%1").arg(taskColumn);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
issuesList.append(issueText);
|
||||
}
|
||||
}
|
||||
|
||||
results.append(QString("Issues found: %1 error(s), %2 warning(s)")
|
||||
.arg(errorCount)
|
||||
.arg(warningCount));
|
||||
|
||||
if (!issuesList.isEmpty()) {
|
||||
results.append("\nDetails:");
|
||||
results.append(issuesList.join("\n\n"));
|
||||
|
||||
if (errorCount + warningCount > 50) {
|
||||
results.append(
|
||||
QString("\n... and %1 more issue(s). Use get_issues_list tool for full list.")
|
||||
.arg(errorCount + warningCount - 50));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
results.append("No compilation errors or warnings.");
|
||||
}
|
||||
|
||||
return results.join("\n");
|
||||
}
|
||||
|
||||
void BuildProjectTool::cleanupBuildInfo(ProjectExplorer::Project *project)
|
||||
{
|
||||
if (!m_activeBuilds.contains(project)) {
|
||||
return;
|
||||
}
|
||||
|
||||
BuildInfo info = m_activeBuilds.take(project);
|
||||
|
||||
if (info.buildFinishedConnection) {
|
||||
disconnect(info.buildFinishedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace QodeAssist::Tools
|
||||
Reference in New Issue
Block a user