CMake: Factor out code to run cmake from tealeafreader

Change-Id: I10947596ce08eb0148a5bf258bdf26cfced43db1
Reviewed-by: Alessandro Portale <alessandro.portale@qt.io>
This commit is contained in:
Tobias Hunger
2019-06-05 18:09:02 +02:00
parent c961d9fc9e
commit daaef73682
7 changed files with 329 additions and 155 deletions

View File

@@ -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

View File

@@ -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 <coreplugin/messagemanager.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <coreplugin/reaper.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/taskhub.h>
#include <utils/algorithm.h>
#include <utils/fileutils.h>
#include <QDir>
#include <QObject>
namespace CMakeProjectManager {
namespace Internal {
using namespace ProjectExplorer;
static QString lineSplit(const QString &rest, const QByteArray &array, std::function<void(const QString &)> 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 &parameters, 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<CMakeParser>();
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<Utils::QtcProcess>();
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<int, QProcess::ExitStatus>::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<QFutureInterface<void>>();
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

View File

@@ -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 <projectexplorer/ioutputparser.h>
#include <utils/qtcprocess.h>
#include <QFutureInterface>
#include <memory>
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 &parameters, 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<Utils::QtcProcess> m_process;
std::unique_ptr<ProjectExplorer::IOutputParser> m_parser;
std::unique_ptr<QFutureInterface<void>> m_future;
};
} // namespace Internal
} // namespace CMakeProjectManager

View File

@@ -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 \

View File

@@ -47,6 +47,8 @@ QtcPlugin {
"cmakelocatorfilter.h",
"cmakeparser.cpp",
"cmakeparser.h",
"cmakeprocess.cpp",
"cmakeprocess.h",
"cmakeproject.cpp",
"cmakeproject.h",
"cmakeproject.qrc",

View File

@@ -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 <coreplugin/documentmanager.h>
#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/idocument.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <coreplugin/reaper.h>
#include <projectexplorer/headerpath.h>
#include <projectexplorer/ioutputparser.h>
#include <projectexplorer/kitinformation.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/target.h>
#include <projectexplorer/task.h>
#include <projectexplorer/taskhub.h>
#include <projectexplorer/toolchain.h>
#include <projectexplorer/toolchainmanager.h>
@@ -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<void(const QString &)> 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<CMakeProcess>();
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<int, QProcess::ExitStatus>::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<void>();
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<QString, QStringList> &cache,
Id lang) const

View File

@@ -28,6 +28,7 @@
#include <projectexplorer/toolchain.h>
#include "builddirreader.h"
#include "cmakeprocess.h"
#include <QRegularExpression>
@@ -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<QString, QStringList> &cache, Core::Id lang) const;
bool extractFlagsFromMake(const CMakeBuildTarget &buildTarget, QHash<QString, QStringList> &cache, Core::Id lang) const;
bool extractFlagsFromNinja(const CMakeBuildTarget &buildTarget, QHash<QString, QStringList> &cache, Core::Id lang) const;
Utils::QtcProcess *m_cmakeProcess = nullptr;
// For error reporting:
ProjectExplorer::IOutputParser *m_parser = nullptr;
QFutureInterface<void> *m_future = nullptr;
// Process data:
std::unique_ptr<CMakeProcess> m_cmakeProcess;
QSet<Utils::FilePath> m_cmakeFiles;
QString m_projectName;