diff --git a/src/plugins/cmakeprojectmanager/CMakeLists.txt b/src/plugins/cmakeprojectmanager/CMakeLists.txt index 6062e40d533..bbfede33aaf 100644 --- a/src/plugins/cmakeprojectmanager/CMakeLists.txt +++ b/src/plugins/cmakeprojectmanager/CMakeLists.txt @@ -20,6 +20,7 @@ add_qtc_plugin(CMakeProjectManager cmakekitinformation.cpp cmakekitinformation.h cmakelocatorfilter.cpp cmakelocatorfilter.h cmakeparser.cpp cmakeparser.h + cmakeprocess.cpp cmakeprocess.h cmakeproject.cpp cmakeproject.h cmakeproject.qrc cmakeprojectconstants.h diff --git a/src/plugins/cmakeprojectmanager/cmakeprocess.cpp b/src/plugins/cmakeprojectmanager/cmakeprocess.cpp new file mode 100644 index 00000000000..4255d89ad01 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/cmakeprocess.cpp @@ -0,0 +1,230 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#include "cmakeprocess.h" + +#include "cmakeparser.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +namespace CMakeProjectManager { +namespace Internal { + +using namespace ProjectExplorer; + +static QString lineSplit(const QString &rest, const QByteArray &array, std::function f) +{ + QString tmp = rest + Utils::SynchronousProcess::normalizeNewlines(QString::fromLocal8Bit(array)); + int start = 0; + int end = tmp.indexOf(QLatin1Char('\n'), start); + while (end >= 0) { + f(tmp.mid(start, end - start)); + start = end + 1; + end = tmp.indexOf(QLatin1Char('\n'), start); + } + return tmp.mid(start); +} + + +CMakeProcess::CMakeProcess() = default; + +CMakeProcess::~CMakeProcess() +{ + if (m_process) { + processStandardOutput(); + processStandardError(); + + m_process->disconnect(); + Core::Reaper::reap(m_process.release()); + } + + // Delete issue parser: + if (m_parser) + m_parser->flush(); + + if (m_future) { + reportCanceled(); + reportFinished(); + } +} + +QStringList CMakeProcess::toArguments(const CMakeConfig &config, + const Utils::MacroExpander *expander) { + return Utils::transform(config, [expander](const CMakeConfigItem &i) -> QString { + return i.toArgument(expander); + }); +} + +void CMakeProcess::run(const BuildDirParameters ¶meters, const QStringList &arguments) +{ + QTC_ASSERT(!m_process && !m_parser && !m_future, return); + + CMakeTool *cmake = parameters.cmakeTool(); + QTC_ASSERT(parameters.isValid() && cmake, return); + + const Utils::FilePath workDirectory = parameters.workDirectory; + QTC_ASSERT(workDirectory.exists(), return); + + const QString srcDir = parameters.sourceDirectory.toString(); + + auto parser = std::make_unique(); + QDir source = QDir(srcDir); + connect(parser.get(), &IOutputParser::addTask, parser.get(), + [source](const Task &task) { + if (task.file.isEmpty() || task.file.toFileInfo().isAbsolute()) { + TaskHub::addTask(task); + } else { + Task t = task; + t.file = Utils::FilePath::fromString(source.absoluteFilePath(task.file.toString())); + TaskHub::addTask(t); + } + }); + + // Always use the sourceDir: If we are triggered because the build directory is getting deleted + // then we are racing against CMakeCache.txt also getting deleted. + + auto process = std::make_unique(); + process->setWorkingDirectory(workDirectory.toString()); + process->setEnvironment(parameters.environment); + + connect(process.get(), &QProcess::readyReadStandardOutput, + this, &CMakeProcess::processStandardOutput); + connect(process.get(), &QProcess::readyReadStandardError, + this, &CMakeProcess::processStandardError); + connect(process.get(), QOverload::of(&QProcess::finished), + this, &CMakeProcess::handleProcessFinished); + + QString args; + Utils::QtcProcess::addArg(&args, srcDir); + Utils::QtcProcess::addArgs(&args, parameters.generatorArguments); + Utils::QtcProcess::addArgs(&args, arguments); + + TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); + + Core::MessageManager::write(tr("Running \"%1 %2\" in %3.") + .arg(cmake->cmakeExecutable().toUserOutput()) + .arg(args) + .arg(workDirectory.toUserOutput())); + + auto future = std::make_unique>(); + future->setProgressRange(0, 1); + Core::ProgressManager::addTask(future->future(), + tr("Configuring \"%1\"").arg(parameters.projectName), + "CMake.Configure"); + + process->setCommand(Utils::CommandLine(cmake->cmakeExecutable(), args, Utils::CommandLine::Raw)); + emit started(); + process->start(); + + m_process = std::move(process); + m_parser = std::move(parser); + m_future = std::move(future); +} + +QProcess::ProcessState CMakeProcess::state() const +{ + if (m_process) + return m_process->state(); + return QProcess::NotRunning; +} + +void CMakeProcess::reportCanceled() +{ + QTC_ASSERT(m_future, return); + m_future->reportCanceled(); +} + +void CMakeProcess::reportFinished() +{ + QTC_ASSERT(m_future, return); + m_future->reportFinished(); + m_future.reset(); +} + +void CMakeProcess::setProgressValue(int p) +{ + QTC_ASSERT(m_future, return); + m_future->setProgressValue(p); +} + +void CMakeProcess::processStandardOutput() +{ + QTC_ASSERT(m_process, return); + + static QString rest; + rest = lineSplit(rest, m_process->readAllStandardOutput(), + [](const QString &s) { Core::MessageManager::write(s); }); + +} + +void CMakeProcess::processStandardError() +{ + QTC_ASSERT(m_process, return); + + static QString rest; + rest = lineSplit(rest, m_process->readAllStandardError(), [this](const QString &s) { + m_parser->stdError(s); + Core::MessageManager::write(s); + }); +} + +void CMakeProcess::handleProcessFinished(int code, QProcess::ExitStatus status) +{ + QTC_ASSERT(m_process && m_future, return); + + processStandardOutput(); + processStandardError(); + + QString msg; + if (status != QProcess::NormalExit) + msg = tr("*** cmake process crashed."); + else if (code != 0) + msg = tr("*** cmake process exited with exit code %1.").arg(code); + + if (!msg.isEmpty()) { + Core::MessageManager::write(msg); + TaskHub::addTask(Task::Error, msg, ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); + m_future->reportCanceled(); + } else { + m_future->setProgressValue(1); + } + + m_future->reportFinished(); + + emit finished(code, status); +} + +} // namespace Internal +} // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakeprocess.h b/src/plugins/cmakeprojectmanager/cmakeprocess.h new file mode 100644 index 00000000000..579a235d237 --- /dev/null +++ b/src/plugins/cmakeprojectmanager/cmakeprocess.h @@ -0,0 +1,77 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of Qt Creator. +** +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +****************************************************************************/ + +#pragma once + +#include "builddirparameters.h" + +#include + +#include + +#include + +#include + +namespace CMakeProjectManager { +namespace Internal { + +class CMakeProcess : public QObject { + Q_OBJECT + +public: + CMakeProcess(); + CMakeProcess(const CMakeProcess&) = delete; + ~CMakeProcess(); + + static QStringList toArguments(const CMakeConfig &config, const Utils::MacroExpander *expander); + + void run(const BuildDirParameters ¶meters, const QStringList &arguments); + + QProcess::ProcessState state() const; + + // Update progress information: + void reportCanceled(); + void reportFinished(); // None of the progress related functions will work after this! + void setProgressValue(int p); + + // Process stdout/stderr: + void processStandardOutput(); + void processStandardError(); + +signals: + void started(); + void finished(int exitCode, QProcess::ExitStatus exitStatus); + +private: + void handleProcessFinished(int code, QProcess::ExitStatus status); + + std::unique_ptr m_process; + std::unique_ptr m_parser; + std::unique_ptr> m_future; +}; + +} // namespace Internal +} // namespace CMakeProjectManager diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro index dc4e30223b0..6d5073f5c6a 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.pro @@ -7,6 +7,7 @@ HEADERS = builddirmanager.h \ cmakebuildstep.h \ cmakebuildtarget.h \ cmakeconfigitem.h \ + cmakeprocess.h \ cmakeproject.h \ cmakeprojectimporter.h \ cmakeprojectplugin.h \ @@ -42,6 +43,7 @@ SOURCES = builddirmanager.cpp \ builddirreader.cpp \ cmakebuildstep.cpp \ cmakeconfigitem.cpp \ + cmakeprocess.cpp \ cmakeproject.cpp \ cmakeprojectimporter.cpp \ cmakeprojectplugin.cpp \ diff --git a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs index e31c42886a9..5157f8ba715 100644 --- a/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs +++ b/src/plugins/cmakeprojectmanager/cmakeprojectmanager.qbs @@ -47,6 +47,8 @@ QtcPlugin { "cmakelocatorfilter.h", "cmakeparser.cpp", "cmakeparser.h", + "cmakeprocess.cpp", + "cmakeprocess.h", "cmakeproject.cpp", "cmakeproject.h", "cmakeproject.qrc", diff --git a/src/plugins/cmakeprojectmanager/tealeafreader.cpp b/src/plugins/cmakeprojectmanager/tealeafreader.cpp index 0fdd4ad1ef9..448d424f8c9 100644 --- a/src/plugins/cmakeprojectmanager/tealeafreader.cpp +++ b/src/plugins/cmakeprojectmanager/tealeafreader.cpp @@ -29,23 +29,17 @@ #include "cmakebuildconfiguration.h" #include "cmakecbpparser.h" #include "cmakekitinformation.h" -#include "cmakeparser.h" +#include "cmakeprocess.h" #include "cmakeprojectconstants.h" #include "cmakeprojectnodes.h" #include #include #include -#include -#include -#include #include -#include #include #include #include -#include -#include #include #include @@ -95,33 +89,14 @@ IDocument::ReloadBehavior CMakeFile::reloadBehavior(ChangeTrigger state, ChangeT bool CMakeFile::reload(QString *errorString, IDocument::ReloadFlag flag, IDocument::ChangeType type) { - Q_UNUSED(errorString); - Q_UNUSED(flag); + Q_UNUSED(errorString) + Q_UNUSED(flag) if (type != TypePermissions) emit m_reader->dirty(); return true; } -static QString lineSplit(const QString &rest, const QByteArray &array, std::function f) -{ - QString tmp = rest + SynchronousProcess::normalizeNewlines(QString::fromLocal8Bit(array)); - int start = 0; - int end = tmp.indexOf(QLatin1Char('\n'), start); - while (end >= 0) { - f(tmp.mid(start, end - start)); - start = end + 1; - end = tmp.indexOf(QLatin1Char('\n'), start); - } - return tmp.mid(start); -} - -static QStringList toArguments(const CMakeConfig &config, const MacroExpander *expander) { - return transform(config, [expander](const CMakeConfigItem &i) -> QString { - return i.toArgument(expander); - }); -} - // -------------------------------------------------------------------- // TeaLeafReader: // -------------------------------------------------------------------- @@ -197,11 +172,13 @@ static QString findCbpFile(const QDir &directory) void TeaLeafReader::parse(bool forceConfiguration) { + emit configurationStarted(); + const QString cbpFile = findCbpFile(QDir(m_parameters.workDirectory.toString())); const QFileInfo cbpFileFi = cbpFile.isEmpty() ? QFileInfo() : QFileInfo(cbpFile); if (!cbpFileFi.exists() || forceConfiguration) { // Initial create: - startCMake(toArguments(m_parameters.configuration, m_parameters.expander)); + startCMake(CMakeProcess::toArguments(m_parameters.configuration, m_parameters.expander)); return; } @@ -219,14 +196,7 @@ void TeaLeafReader::parse(bool forceConfiguration) void TeaLeafReader::stop() { - cleanUpProcess(); - - if (m_future) { - m_future->reportCanceled(); - m_future->reportFinished(); - delete m_future; - m_future = nullptr; - } + m_cmakeProcess.reset(); } bool TeaLeafReader::isParsing() const @@ -400,21 +370,6 @@ CppTools::RawProjectParts TeaLeafReader::createRawProjectParts() const return rpps; } -void TeaLeafReader::cleanUpProcess() -{ - if (m_cmakeProcess) { - m_cmakeProcess->disconnect(); - Reaper::reap(m_cmakeProcess); - m_cmakeProcess = nullptr; - } - - // Delete issue parser: - if (m_parser) - m_parser->flush(); - delete m_parser; - m_parser = nullptr; -} - void TeaLeafReader::extractData() { CMakeTool *cmake = m_parameters.cmakeTool(); @@ -469,117 +424,29 @@ void TeaLeafReader::extractData() void TeaLeafReader::startCMake(const QStringList &configurationArguments) { - CMakeTool *cmake = m_parameters.cmakeTool(); - QTC_ASSERT(m_parameters.isValid() && cmake, return); - - const FilePath workDirectory = m_parameters.workDirectory; QTC_ASSERT(!m_cmakeProcess, return); - QTC_ASSERT(!m_parser, return); - QTC_ASSERT(!m_future, return); - QTC_ASSERT(workDirectory.exists(), return); - const QString srcDir = m_parameters.sourceDirectory.toString(); + m_cmakeProcess = std::make_unique(); - m_parser = new CMakeParser; - QDir source = QDir(srcDir); - connect(m_parser, &IOutputParser::addTask, m_parser, - [source](const Task &task) { - if (task.file.isEmpty() || task.file.toFileInfo().isAbsolute()) { - TaskHub::addTask(task); - } else { - Task t = task; - t.file = FilePath::fromString(source.absoluteFilePath(task.file.toString())); - TaskHub::addTask(t); - } - }); - - // Always use the sourceDir: If we are triggered because the build directory is getting deleted - // then we are racing against CMakeCache.txt also getting deleted. - - m_cmakeProcess = new QtcProcess; - m_cmakeProcess->setWorkingDirectory(workDirectory.toString()); - m_cmakeProcess->setEnvironment(m_parameters.environment); - - connect(m_cmakeProcess, &QProcess::readyReadStandardOutput, - this, &TeaLeafReader::processCMakeOutput); - connect(m_cmakeProcess, &QProcess::readyReadStandardError, - this, &TeaLeafReader::processCMakeError); - connect(m_cmakeProcess, QOverload::of(&QProcess::finished), + connect(m_cmakeProcess.get(), &CMakeProcess::finished, this, &TeaLeafReader::cmakeFinished); - QString args; - QtcProcess::addArg(&args, srcDir); - QtcProcess::addArgs(&args, m_parameters.generatorArguments); - QtcProcess::addArgs(&args, configurationArguments); - - TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); - - MessageManager::write(tr("Running \"%1 %2\" in %3.") - .arg(cmake->cmakeExecutable().toUserOutput()) - .arg(args) - .arg(workDirectory.toUserOutput())); - - m_future = new QFutureInterface(); - m_future->setProgressRange(0, 1); - ProgressManager::addTask(m_future->future(), - tr("Configuring \"%1\"").arg(m_parameters.projectName), - "CMake.Configure"); - - m_cmakeProcess->setCommand(CommandLine(cmake->cmakeExecutable(), args, CommandLine::Raw)); - emit configurationStarted(); - m_cmakeProcess->start(); + m_cmakeProcess->run(m_parameters, configurationArguments); } void TeaLeafReader::cmakeFinished(int code, QProcess::ExitStatus status) { + Q_UNUSED(code) + Q_UNUSED(status) + QTC_ASSERT(m_cmakeProcess, return); - - // process rest of the output: - processCMakeOutput(); - processCMakeError(); - - m_cmakeProcess->disconnect(); - cleanUpProcess(); + m_cmakeProcess.reset(); extractData(); // try even if cmake failed... - QString msg; - if (status != QProcess::NormalExit) - msg = tr("*** cmake process crashed."); - else if (code != 0) - msg = tr("*** cmake process exited with exit code %1.").arg(code); - - if (!msg.isEmpty()) { - MessageManager::write(msg); - TaskHub::addTask(Task::Error, msg, ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); - m_future->reportCanceled(); - } else { - m_future->setProgressValue(1); - } - - m_future->reportFinished(); - delete m_future; - m_future = nullptr; - emit dataAvailable(); } -void TeaLeafReader::processCMakeOutput() -{ - static QString rest; - rest = lineSplit(rest, m_cmakeProcess->readAllStandardOutput(), - [](const QString &s) { MessageManager::write(s); }); -} - -void TeaLeafReader::processCMakeError() -{ - static QString rest; - rest = lineSplit(rest, m_cmakeProcess->readAllStandardError(), [this](const QString &s) { - m_parser->stdError(s); - MessageManager::write(s); - }); -} - QStringList TeaLeafReader::getFlagsFor(const CMakeBuildTarget &buildTarget, QHash &cache, Id lang) const diff --git a/src/plugins/cmakeprojectmanager/tealeafreader.h b/src/plugins/cmakeprojectmanager/tealeafreader.h index 6e288cc54f0..847f38d3aa0 100644 --- a/src/plugins/cmakeprojectmanager/tealeafreader.h +++ b/src/plugins/cmakeprojectmanager/tealeafreader.h @@ -28,6 +28,7 @@ #include #include "builddirreader.h" +#include "cmakeprocess.h" #include @@ -62,24 +63,18 @@ public: CppTools::RawProjectParts createRawProjectParts() const final; private: - void cleanUpProcess(); void extractData(); void startCMake(const QStringList &configurationArguments); void cmakeFinished(int code, QProcess::ExitStatus status); - void processCMakeOutput(); - void processCMakeError(); QStringList getFlagsFor(const CMakeBuildTarget &buildTarget, QHash &cache, Core::Id lang) const; bool extractFlagsFromMake(const CMakeBuildTarget &buildTarget, QHash &cache, Core::Id lang) const; bool extractFlagsFromNinja(const CMakeBuildTarget &buildTarget, QHash &cache, Core::Id lang) const; - Utils::QtcProcess *m_cmakeProcess = nullptr; - - // For error reporting: - ProjectExplorer::IOutputParser *m_parser = nullptr; - QFutureInterface *m_future = nullptr; + // Process data: + std::unique_ptr m_cmakeProcess; QSet m_cmakeFiles; QString m_projectName;