Commit 18afa345 authored by Tobias Hunger's avatar Tobias Hunger

CMake: Add CMakeConfigurationKitInformation

Add CMakeConfigurationKitInformation and supporting classes.

This KitInformation can hold CMake settings that are to be applied
for all configurations when this kit is used. It contains e.g.
the Qt version and toolchain configured in the kit by default, but
can be extended to hold more.

The UI will warn if the toolchain or Qt version set up in CMake
does not match up with those used by the Kit, but it will allow
the user to proceed.

Change-Id: I73f06a6535ce14de323130d74af83b9cb2ec0f0f
Reviewed-by: default avatarLeena Miettinen <riitta-leena.miettinen@theqtcompany.com>
Reviewed-by: default avatarTim Jenssen <tim.jenssen@theqtcompany.com>
parent 3447f8d9
/****************************************************************************
**
** 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 "cmakeconfigitem.h"
#include <utils/qtcassert.h>
namespace CMakeProjectManager {
// --------------------------------------------------------------------
// CMakeConfigItem:
// --------------------------------------------------------------------
CMakeConfigItem::CMakeConfigItem() = default;
CMakeConfigItem::CMakeConfigItem(const CMakeConfigItem &other) :
key(other.key), type(other.type), isAdvanced(other.isAdvanced),
value(other.value), documentation(other.documentation)
{ }
CMakeConfigItem::CMakeConfigItem(const QByteArray &k, CMakeConfigItem::Type &t,
const QByteArray &d, const QByteArray &v) :
key(k), type(t), value(v), documentation(d)
{ }
CMakeConfigItem::CMakeConfigItem(const QByteArray &k, const QByteArray &v) :
key(k), value(v)
{ }
std::function<bool (const CMakeConfigItem &a, const CMakeConfigItem &b)> CMakeConfigItem::sortOperator()
{
return [](const CMakeConfigItem &a, const CMakeConfigItem &b) { return a.key < b.key; };
}
CMakeConfigItem CMakeConfigItem::fromString(const QString &s)
{
// Strip comments:
int commentStart = s.count();
int pos = s.indexOf(QLatin1Char('#'));
if (pos >= 0)
commentStart = pos;
pos = s.indexOf(QLatin1String("//"));
if (pos >= 0 && pos < commentStart)
commentStart = pos;
const QString line = s.mid(0, commentStart);
// Split up line:
int firstPos = -1;
int colonPos = -1;
int equalPos = -1;
for (int i = 0; i < line.count(); ++i) {
const QChar c = s.at(i);
if (firstPos < 0 && !c.isSpace()) {
firstPos = i;
}
if (c == QLatin1Char(':')) {
if (colonPos > 0)
break;
colonPos = i;
continue;
}
if (c == QLatin1Char('=')) {
equalPos = i;
break;
}
}
QString key;
QString type;
QString value;
if (equalPos >= 0) {
key = line.mid(firstPos, ((colonPos >= 0) ? colonPos : equalPos) - firstPos);
type = (colonPos < 0) ? QString() : line.mid(colonPos + 1, equalPos - colonPos - 1);
value = line.mid(equalPos + 1);
}
// Fill in item:
CMakeConfigItem item;
if (!key.isEmpty()) {
CMakeConfigItem::Type t = CMakeConfigItem::STRING;
if (type == QLatin1String("FILEPATH"))
t = CMakeConfigItem::FILEPATH;
else if (type == QLatin1String("PATH"))
t = CMakeConfigItem::PATH;
else if (type == QLatin1String("BOOL"))
t = CMakeConfigItem::BOOL;
else if (type == QLatin1String("INTERNAL"))
t = CMakeConfigItem::INTERNAL;
item.key = key.toUtf8();
item.type = t;
item.value = value.toUtf8();
}
return item;
}
QString CMakeConfigItem::toString() const
{
if (key.isEmpty())
return QString();
QString typeStr;
switch (type)
{
case CMakeProjectManager::CMakeConfigItem::FILEPATH:
typeStr = QLatin1String("FILEPATH");
break;
case CMakeProjectManager::CMakeConfigItem::PATH:
typeStr = QLatin1String("PATH");
break;
case CMakeProjectManager::CMakeConfigItem::BOOL:
typeStr = QLatin1String("BOOL");
break;
case CMakeProjectManager::CMakeConfigItem::INTERNAL:
typeStr = QLatin1String("INTERNAL");
break;
case CMakeProjectManager::CMakeConfigItem::STRING:
default:
typeStr = QLatin1String("STRING");
break;
}
return QString::fromUtf8(key) + QLatin1Char(':') + typeStr + QLatin1Char('=') + QString::fromUtf8(value);
}
} // namespace CMakeProjectManager
/****************************************************************************
**
** 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 <QByteArray>
#include <QList>
#include <functional>
namespace CMakeProjectManager {
class CMakeConfigItem {
public:
enum Type { FILEPATH, PATH, BOOL, STRING, INTERNAL };
CMakeConfigItem();
CMakeConfigItem(const CMakeConfigItem &other);
CMakeConfigItem(const QByteArray &k, Type &t, const QByteArray &d, const QByteArray &v);
CMakeConfigItem(const QByteArray &k, const QByteArray &v);
bool isNull() const { return key.isEmpty(); }
static std::function<bool(const CMakeConfigItem &a, const CMakeConfigItem &b)> sortOperator();
static CMakeConfigItem fromString(const QString &s);
QString toString() const;
QByteArray key;
Type type = STRING;
bool isAdvanced = false;
QByteArray value; // converted to string as needed
QByteArray documentation;
};
using CMakeConfig = QList<CMakeConfigItem>;
} // namespace CMakeProjectManager
......@@ -33,9 +33,15 @@
#include <projectexplorer/kit.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
#include <QBoxLayout>
#include <QComboBox>
#include <QDialog>
#include <QDialogButtonBox>
#include <QLabel>
#include <QPlainTextEdit>
#include <QPushButton>
using namespace ProjectExplorer;
......@@ -263,5 +269,114 @@ QString CMakeGeneratorKitConfigWidget::toolTip() const
"This setting is ignored when using other build systems.");
}
// --------------------------------------------------------------------
// CMakeConfigurationKitConfigWidget:
// --------------------------------------------------------------------
CMakeConfigurationKitConfigWidget::CMakeConfigurationKitConfigWidget(Kit *kit,
const KitInformation *ki) :
KitConfigWidget(kit, ki),
m_summaryLabel(new QLabel),
m_manageButton(new QPushButton)
{
refresh();
m_manageButton->setText(tr("Change..."));
connect(m_manageButton, &QAbstractButton::clicked,
this, &CMakeConfigurationKitConfigWidget::editConfigurationChanges);
}
QString CMakeConfigurationKitConfigWidget::displayName() const
{
return tr("CMake Configuration");
}
void CMakeConfigurationKitConfigWidget::makeReadOnly()
{
m_manageButton->setEnabled(false);
if (m_dialog)
m_dialog->reject();
}
void CMakeConfigurationKitConfigWidget::refresh()
{
const QStringList current = CMakeConfigurationKitInformation::toStringList(kit());
QString shortSummary = current.join(QLatin1String("; "));
QFontMetrics fm(m_summaryLabel->font());
shortSummary = fm.elidedText(shortSummary, Qt::ElideRight, m_summaryLabel->width());
m_summaryLabel->setText(current.isEmpty() ? tr("<No Changes to Apply>") : shortSummary);
if (m_editor)
m_editor->setPlainText(current.join(QLatin1Char('\n')));
}
QWidget *CMakeConfigurationKitConfigWidget::mainWidget() const
{
return m_summaryLabel;
}
QWidget *CMakeConfigurationKitConfigWidget::buttonWidget() const
{
return m_manageButton;
}
QString CMakeConfigurationKitConfigWidget::toolTip() const
{
return tr("Default configuration passed to CMake when setting up a project.");
}
void CMakeConfigurationKitConfigWidget::editConfigurationChanges()
{
if (m_dialog) {
m_dialog->activateWindow();
m_dialog->raise();
return;
}
QTC_ASSERT(!m_editor, return);
m_dialog = new QDialog(m_summaryLabel);
m_dialog->setWindowTitle(tr("Edit CMake Configuration"));
QVBoxLayout *layout = new QVBoxLayout(m_dialog);
m_editor = new QPlainTextEdit;
m_editor->setToolTip(tr("Enter one variable per line with the variable name "
"separated from the variable value by \"=\".<br>"
"You may provide a type hint by adding \":TYPE\" before the \"=\"."));
QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Apply|QDialogButtonBox::Cancel);
layout->addWidget(m_editor);
layout->addWidget(buttons);
connect(buttons, &QDialogButtonBox::accepted, m_dialog, &QDialog::accept);
connect(buttons, &QDialogButtonBox::rejected, m_dialog, &QDialog::reject);
connect(m_dialog, &QDialog::accepted, this, &CMakeConfigurationKitConfigWidget::acceptChangesDialog);
connect(m_dialog, &QDialog::rejected, this, &CMakeConfigurationKitConfigWidget::closeChangesDialog);
connect(buttons->button(QDialogButtonBox::Apply), &QAbstractButton::clicked,
this, &CMakeConfigurationKitConfigWidget::applyChanges);
refresh();
m_dialog->show();
}
void CMakeConfigurationKitConfigWidget::applyChanges()
{
QTC_ASSERT(m_editor, return);
CMakeConfigurationKitInformation::fromStringList(kit(), m_editor->toPlainText().split(QLatin1Char('\n')));
}
void CMakeConfigurationKitConfigWidget::closeChangesDialog()
{
m_dialog->deleteLater();
m_dialog = nullptr;
m_editor = nullptr;
}
void CMakeConfigurationKitConfigWidget::acceptChangesDialog()
{
applyChanges();
closeChangesDialog();
}
} // namespace Internal
} // namespace CMakeProjectManager
......@@ -27,13 +27,17 @@
#include <projectexplorer/kitconfigwidget.h>
QT_BEGIN_NAMESPACE
class QComboBox;
class QLabel;
class QPlainTextEdit;
class QPushButton;
QT_END_NAMESPACE
namespace ProjectExplorer {
class Kit;
class KitInformation;
}
QT_FORWARD_DECLARE_CLASS(QComboBox)
QT_FORWARD_DECLARE_CLASS(QPushButton)
} // namespace ProjectExplorer
namespace CMakeProjectManager {
......@@ -99,5 +103,36 @@ private:
CMakeTool *m_currentTool = nullptr;
};
// --------------------------------------------------------------------
// CMakeConfigurationKitConfigWidget:
// --------------------------------------------------------------------
class CMakeConfigurationKitConfigWidget : public ProjectExplorer::KitConfigWidget
{
Q_OBJECT
public:
CMakeConfigurationKitConfigWidget(ProjectExplorer::Kit *kit, const ProjectExplorer::KitInformation *ki);
// KitConfigWidget interface
QString displayName() const override;
void makeReadOnly() override;
void refresh() override;
QWidget *mainWidget() const override;
QWidget *buttonWidget() const override;
QString toolTip() const override;
private:
void editConfigurationChanges();
void applyChanges();
void closeChangesDialog();
void acceptChangesDialog();
QLabel *m_summaryLabel;
QPushButton *m_manageButton;
QDialog *m_dialog = nullptr;
QPlainTextEdit *m_editor = nullptr;
};
} // namespace Internal
} // namespace CMakeProjectManager
......@@ -29,9 +29,13 @@
#include "cmaketool.h"
#include <projectexplorer/task.h>
#include <projectexplorer/toolchain.h>
#include <projectexplorer/kit.h>
#include <projectexplorer/kitinformation.h>
#include <qtsupport/qtkitinformation.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <utils/algorithm.h>
#include <utils/qtcassert.h>
using namespace ProjectExplorer;
......@@ -237,4 +241,179 @@ KitConfigWidget *CMakeGeneratorKitInformation::createConfigWidget(Kit *k) const
return new Internal::CMakeGeneratorKitConfigWidget(k, this);
}
// --------------------------------------------------------------------
// CMakeConfigurationKitInformation:
// --------------------------------------------------------------------
static const char CONFIGURATION_ID[] = "CMake.ConfigurationKitInformation";
static const char CMAKE_QMAKE_KEY[] = "QT_QMAKE_EXECUTABLE";
static const char CMAKE_TOOLCHAIN_KEY[] = "CMAKE_CXX_COMPILER";
CMakeConfigurationKitInformation::CMakeConfigurationKitInformation()
{
setObjectName(QLatin1String("CMakeConfigurationKitInformation"));
setId(CONFIGURATION_ID);
setPriority(18000);
}
CMakeConfig CMakeConfigurationKitInformation::configuration(const Kit *k)
{
if (!k)
return CMakeConfig();
const QStringList tmp = k->value(CONFIGURATION_ID).toStringList();
return Utils::transform(tmp, [](const QString &s) { return CMakeConfigItem::fromString(s); });
}
void CMakeConfigurationKitInformation::setConfiguration(Kit *k, const CMakeConfig &config)
{
if (!k)
return;
const QStringList tmp = Utils::transform(config, [](const CMakeConfigItem &i) { return i.toString(); });
k->setValue(CONFIGURATION_ID, tmp);
}
QStringList CMakeConfigurationKitInformation::toStringList(const Kit *k)
{
QStringList current
= Utils::transform(CMakeConfigurationKitInformation::configuration(k),
[](const CMakeConfigItem &i) { return i.toString(); });
Utils::sort(current);
return current;
}
void CMakeConfigurationKitInformation::fromStringList(Kit *k, const QStringList &in)
{
CMakeConfig result;
foreach (const QString &s, in) {
const CMakeConfigItem item = CMakeConfigItem::fromString(s);
if (!item.key.isEmpty())
result << item;
}
setConfiguration(k, result);
}
QVariant CMakeConfigurationKitInformation::defaultValue(const Kit *k) const
{
// FIXME: Convert preload scripts
CMakeConfig config;
const QtSupport::BaseQtVersion *const version = QtSupport::QtKitInformation::qtVersion(k);
if (version && version->isValid())
config << CMakeConfigItem(CMAKE_QMAKE_KEY, version->qmakeCommand().toString().toUtf8());
const ToolChain *const tc = ToolChainKitInformation::toolChain(k);
if (tc && tc->isValid())
config << CMakeConfigItem(CMAKE_TOOLCHAIN_KEY, tc->compilerCommand().toString().toUtf8());
const QStringList tmp
= Utils::transform(config, [](const CMakeConfigItem &i) { return i.toString(); });
return tmp;
}
QList<Task> CMakeConfigurationKitInformation::validate(const Kit *k) const
{
const QtSupport::BaseQtVersion *const version = QtSupport::QtKitInformation::qtVersion(k);
const ToolChain *const tc = ToolChainKitInformation::toolChain(k);
const CMakeConfig config = configuration(k);
QByteArray qmakePath;
QByteArray tcPath;
foreach (const CMakeConfigItem &i, config) {
if (i.key == CMAKE_QMAKE_KEY)
qmakePath = i.value;
else if (i.key == CMAKE_TOOLCHAIN_KEY)
tcPath = i.value;
}
QList<Task> result;
// Validate Qt:
if (qmakePath.isEmpty()) {
if (version && version->isValid()) {
result << Task(Task::Warning, tr("CMake configuration has no path to qmake binary set, "
"even though the kit has a valid Qt version."),
Utils::FileName(), -1, Core::Id(Constants::TASK_CATEGORY_BUILDSYSTEM));
}
} else {
if (!version || !version->isValid()) {
result << Task(Task::Warning, tr("CMake configuration has a path to a qmake binary set, "
"even though the kit has no valid Qt version."),
Utils::FileName(), -1, Core::Id(Constants::TASK_CATEGORY_BUILDSYSTEM));
} else if (qmakePath != version->qmakeCommand().toString().toUtf8()) {
result << Task(Task::Warning, tr("CMake configuration has a path to a qmake binary set, "
"which does not match up with the qmake binary path "
"configured in the Qt version."),
Utils::FileName(), -1, Core::Id(Constants::TASK_CATEGORY_BUILDSYSTEM));
}
}
// Validate Toolchain:
if (tcPath.isEmpty()) {
if (tc && tc->isValid()) {
result << Task(Task::Warning, tr("CMake configuration has no path to a C++ compiler set, "
"even though the kit has a valid tool chain."),
Utils::FileName(), -1, Core::Id(Constants::TASK_CATEGORY_BUILDSYSTEM));
}
} else {
if (!tc || !tc->isValid()) {
result << Task(Task::Warning, tr("CMake configuration has a path to a C++ compiler set, "
"even though the kit has no valid tool chain."),
Utils::FileName(), -1, Core::Id(Constants::TASK_CATEGORY_BUILDSYSTEM));
} else if (tcPath != tc->compilerCommand().toString().toUtf8()) {
result << Task(Task::Warning, tr("CMake configuration has a path to a C++ compiler set, "
"that does not match up with the compiler path "
"configured in the tool chain of the kit."),
Utils::FileName(), -1, Core::Id(Constants::TASK_CATEGORY_BUILDSYSTEM));
}
}
return result;
}
void CMakeConfigurationKitInformation::setup(Kit *k)
{
if (k)
k->setValue(CONFIGURATION_ID, defaultValue(k));
}
void CMakeConfigurationKitInformation::fix(Kit *k)
{
const QtSupport::BaseQtVersion *const version = QtSupport::QtKitInformation::qtVersion(k);
const QByteArray qmakePath
= (version && version->isValid()) ? version->qmakeCommand().toString().toUtf8() : QByteArray();
const ToolChain *const tc = ToolChainKitInformation::toolChain(k);
const QByteArray tcPath
= (tc && tc->isValid()) ? tc->compilerCommand().toString().toUtf8() : QByteArray();
CMakeConfig result;
bool haveQmake = false;
bool haveToolChain = false;
foreach (const CMakeConfigItem &i, configuration(k)) {
if (i.key == CMAKE_QMAKE_KEY)
haveQmake = true;
else if (i.key == CMAKE_TOOLCHAIN_KEY)
haveToolChain = true;
result << i;
}
if (!haveQmake && !qmakePath.isEmpty())
result << CMakeConfigItem(CMAKE_QMAKE_KEY, qmakePath);
if (!haveToolChain && !tcPath.isEmpty())
result << CMakeConfigItem(CMAKE_TOOLCHAIN_KEY, tcPath);
setConfiguration(k, result);
}
KitInformation::ItemList CMakeConfigurationKitInformation::toUserOutput(const Kit *k) const
{
const QStringList current = toStringList(k);
return ItemList() << qMakePair(tr("CMake Configuration"), current.join(QLatin1String("<br>")));
}
KitConfigWidget *CMakeConfigurationKitInformation::createConfigWidget(Kit *k) const
{
if (!k)
return 0;
return new Internal::CMakeConfigurationKitConfigWidget(k, this);
}
} // namespace CMakeProjectManager
......@@ -27,6 +27,8 @@
#include "cmake_global.h"
#include "cmakeconfigitem.h"
#include <projectexplorer/kitmanager.h>
namespace CMakeProjectManager {
......@@ -70,4 +72,25 @@ public:
ProjectExplorer::KitConfigWidget *createConfigWidget(ProjectExplorer::Kit *k) const override;
};
class CMAKE_EXPORT CMakeConfigurationKitInformation : public ProjectExplorer::KitInformation
{
Q_OBJECT
public:
CMakeConfigurationKitInformation();
static CMakeConfig configuration(const ProjectExplorer::Kit *k);
static void setConfiguration(ProjectExplorer::Kit *k, const CMakeConfig &config);