diff --git a/src/plugins/cmakeprojectmanager/CMakeLists.txt b/src/plugins/cmakeprojectmanager/CMakeLists.txt index 71830cf9b87..b2434364a4b 100644 --- a/src/plugins/cmakeprojectmanager/CMakeLists.txt +++ b/src/plugins/cmakeprojectmanager/CMakeLists.txt @@ -17,6 +17,7 @@ add_qtc_plugin(CMakeProjectManager cmakeeditor.cpp cmakeeditor.h cmakefilecompletionassist.cpp cmakefilecompletionassist.h cmakeformatter.cpp cmakeformatter.h + cmakehooks.cpp cmakehooks.h cmakeindenter.cpp cmakeindenter.h cmakeinstallstep.cpp cmakeinstallstep.h cmakekitaspect.cpp cmakekitaspect.h diff --git a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp index ca23831cbaa..84088734c1c 100644 --- a/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp +++ b/src/plugins/cmakeprojectmanager/cmakebuildsystem.cpp @@ -7,6 +7,7 @@ #include "cmakebuildconfiguration.h" #include "cmakebuildstep.h" #include "cmakebuildtarget.h" +#include "cmakehooks.h" #include "cmakekitaspect.h" #include "cmakeprocess.h" #include "cmakeproject.h" @@ -1341,6 +1342,8 @@ void CMakeBuildSystem::setParametersAndRequestParse(const BuildDirParameters &pa return; // ignore request, this build configuration is not active! } + CMakeHooks::runHook("BeforeCMakeRun", parameters.project->projectDirectory()); + const CMakeTool *tool = parameters.cmakeTool(); if (!tool || !tool->isValid()) { TaskHub::addTask( diff --git a/src/plugins/cmakeprojectmanager/cmakehooks.cpp b/src/plugins/cmakeprojectmanager/cmakehooks.cpp new file mode 100644 index 00000000000..b21b96ac634 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/cmakehooks.cpp @@ -0,0 +1,124 @@ +#include "cmakehooks.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace Core; +using namespace Utils; + +namespace { + +QJsonObject readHooks(const FilePath &projectDir) +{ + const FilePath hooksFile = projectDir.pathAppended("QtCreatorHooks.json"); + QFile file(hooksFile.toUrlishString()); + if (!file.exists() || !file.open(QIODevice::ReadOnly)) { + qWarning() << "No hooks file found at:" << hooksFile.toUrlishString(); + return {}; + } + + const QByteArray data = file.readAll(); + QJsonParseError parseError; + const QJsonDocument doc = QJsonDocument::fromJson(data, &parseError); + if (parseError.error != QJsonParseError::NoError || !doc.isObject()) { + qWarning() << "Failed to parse hooks JSON:" << parseError.errorString(); + return {}; + } + + return doc.object(); +} + +QString getHookCommand(const QJsonObject &hooks, const QString &hookName) +{ + if (!hooks.contains(hookName)) + return {}; + + const QJsonValue val = hooks.value(hookName); + return val.isString() ? val.toString() : QString(); +} + +} // namespace + +namespace CMakeHooks { + +bool runHook(const QString &hookName, const FilePath &projectDir) +{ + const QJsonObject hooks = readHooks(projectDir); + const QString commandLine = getHookCommand(hooks, hookName); + if (commandLine.isEmpty()) { + return false; + } + + QStringList args = QProcess::splitCommand(commandLine); + if (args.isEmpty()) { + return false; + } + + const QString program = args.takeFirst(); + + auto hookProcess = QSharedPointer::create(); + auto weakProcess = QWeakPointer(hookProcess); + hookProcess->setWorkingDirectory(projectDir.toUrlishString()); + hookProcess->setProcessEnvironment(QProcessEnvironment::systemEnvironment()); + + MessageManager::writeFlashing(QStringLiteral("Running hook '%1': %2 %3") + .arg(hookName, program, args.join(' '))); + + QObject::connect(hookProcess.get(), &QProcess::readyReadStandardOutput, [weakProcess, hookName]() { + const auto hookProcess = weakProcess.toStrongRef(); + while (hookProcess->canReadLine()) { + const QString line = QString::fromUtf8(hookProcess->readLine()).trimmed(); + MessageManager::writeFlashing(QStringLiteral("[hook:%1] %2").arg(hookName, line)); + } + }); + + QObject::connect(hookProcess.get(), &QProcess::readyReadStandardError, [weakProcess, hookName]() { + const auto hookProcess = weakProcess.toStrongRef(); + const QString err = QString::fromUtf8(hookProcess->readAllStandardError()).trimmed(); + if (!err.isEmpty()) { + MessageManager::writeDisrupting(QStringLiteral("Hook stderr [%1]:\n%2").arg(hookName, err)); + } + }); + + QEventLoop loop; + QObject::connect(hookProcess.get(), &QProcess::finished, &loop, &QEventLoop::quit); + QObject::connect(hookProcess.get(), &QProcess::errorOccurred, &loop, &QEventLoop::quit); + + hookProcess->start(program, args); + + if (!hookProcess->waitForStarted()) { + const QString errorMsg = QStringLiteral("Failed to start hook '%1': %2") + .arg(hookName, hookProcess->errorString()); + MessageManager::writeDisrupting(errorMsg); + return false; + } + + // wait for the process to finish and don't block the UI + loop.exec(); + + const int exitCode = hookProcess->exitCode(); + const bool success = hookProcess->exitStatus() == QProcess::NormalExit && exitCode == 0; + + if (!success) { + const QString failMsg = QStringLiteral("Hook '%1' failed with exit code %2") + .arg(hookName) + .arg(exitCode); + MessageManager::writeDisrupting(failMsg); + return false; + } + + MessageManager::writeFlashing(QStringLiteral("Hook '%1' finished successfully.").arg(hookName)); + return true; +} + + +} // namespace CMakeHooks diff --git a/src/plugins/cmakeprojectmanager/cmakehooks.h b/src/plugins/cmakeprojectmanager/cmakehooks.h new file mode 100644 index 00000000000..9481d6ff1ef --- /dev/null +++ b/src/plugins/cmakeprojectmanager/cmakehooks.h @@ -0,0 +1,15 @@ +#pragma once + +#include + +namespace Utils { + +class FilePath; + +} + +namespace CMakeHooks { + +bool runHook(const QString &hookName, const Utils::FilePath &projectDir); + +} diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp index a8417a070a2..58647255459 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp @@ -4,6 +4,7 @@ #include "cmakeproject.h" #include "cmakebuildsystem.h" +#include "cmakehooks.h" #include "cmakekitaspect.h" #include "cmakeprojectconstants.h" #include "cmakeprojectimporter.h" @@ -51,6 +52,8 @@ CMakeProject::CMakeProject(const FilePath &fileName) : Project(Utils::Constants::CMAKE_MIMETYPE, cmakeListTxtFromFilePath(fileName)) , m_settings(this, true) { + CMakeHooks::runHook("BeforeOpenCMakeProject", projectFilePath().absolutePath()); + setId(CMakeProjectManager::Constants::CMAKE_PROJECT_ID); setProjectLanguages(Core::Context(ProjectExplorer::Constants::CXX_LANGUAGE_ID)); setDisplayName(projectDisplayName(projectFilePath()));