diff --git a/kdevplatform/shell/runtimecontroller.cpp b/kdevplatform/shell/runtimecontroller.cpp index c016cfacd2..b33617e098 100644 --- a/kdevplatform/shell/runtimecontroller.cpp +++ b/kdevplatform/shell/runtimecontroller.cpp @@ -1,165 +1,165 @@ /* Copyright 2017 Aleix Pol Gonzalez This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "runtimecontroller.h" #include #include #include #include #include #include #include "core.h" #include "uicontroller.h" #include "mainwindow.h" #include "debug.h" using namespace KDevelop; class IdentityRuntime : public IRuntime { QString name() const override { return i18n("Host System"); } void startProcess(KProcess *process) const override { #if QT_VERSION < 0x050600 connect(process, static_cast(&QProcess::error), #else connect(process, &QProcess::errorOccurred, #endif - this, [process](QProcess::ProcessError error) { + this, [](QProcess::ProcessError error) { qCWarning(SHELL) << "process finished with error:" << error; }); process->start(); } void startProcess(QProcess *process) const override { #if QT_VERSION < 0x050600 connect(process, static_cast(&QProcess::error), #else connect(process, &QProcess::errorOccurred, #endif - this, [process](QProcess::ProcessError error) { + this, [](QProcess::ProcessError error) { qCWarning(SHELL) << "process finished with error:" << error; }); process->start(); } KDevelop::Path pathInHost(const KDevelop::Path & runtimePath) const override { return runtimePath; } KDevelop::Path pathInRuntime(const KDevelop::Path & localPath) const override { return localPath; } void setEnabled(bool /*enabled*/) override {} QByteArray getenv(const QByteArray & varname) const override { return qgetenv(varname.constData()); } }; KDevelop::RuntimeController::RuntimeController(KDevelop::Core* core) : m_core(core) { const bool haveUI = (core->setupFlags() != Core::NoUi); if (haveUI) { m_runtimesMenu.reset(new QMenu()); } addRuntimes(new IdentityRuntime); setCurrentRuntime(m_runtimes.first()); if (haveUI) { setupActions(); } } KDevelop::RuntimeController::~RuntimeController() { m_currentRuntime->setEnabled(false); m_currentRuntime = nullptr; } void RuntimeController::setupActions() { // TODO not multi-window friendly, FIXME KActionCollection* ac = m_core->uiControllerInternal()->defaultMainWindow()->actionCollection(); auto action = new QAction(this); action->setStatusTip(i18n("Allows to select a runtime")); action->setMenu(m_runtimesMenu.data()); action->setIcon(QIcon::fromTheme(QStringLiteral("file-library-symbolic"))); auto updateActionText = [action](IRuntime* currentRuntime){ action->setText(i18n("Runtime: %1", currentRuntime->name())); }; connect(this, &RuntimeController::currentRuntimeChanged, action, updateActionText); updateActionText(m_currentRuntime); ac->addAction(QStringLiteral("switch_runtimes"), action); } void KDevelop::RuntimeController::initialize() { } KDevelop::IRuntime * KDevelop::RuntimeController::currentRuntime() const { Q_ASSERT(m_currentRuntime); return m_currentRuntime; } QVector KDevelop::RuntimeController::availableRuntimes() const { return m_runtimes; } void KDevelop::RuntimeController::setCurrentRuntime(KDevelop::IRuntime* runtime) { if (m_currentRuntime == runtime) return; Q_ASSERT(m_runtimes.contains(runtime)); if (m_currentRuntime) { m_currentRuntime->setEnabled(false); } qCDebug(SHELL) << "setting runtime..." << runtime->name() << "was" << m_currentRuntime; m_currentRuntime = runtime; m_currentRuntime->setEnabled(true); Q_EMIT currentRuntimeChanged(runtime); } void KDevelop::RuntimeController::addRuntimes(KDevelop::IRuntime * runtime) { if (!runtime->parent()) runtime->setParent(this); if (m_core->setupFlags() != Core::NoUi) { QAction* runtimeAction = new QAction(runtime->name(), m_runtimesMenu.data()); runtimeAction->setCheckable(true); connect(runtimeAction, &QAction::triggered, runtime, [this, runtime]() { setCurrentRuntime(runtime); }); connect(this, &RuntimeController::currentRuntimeChanged, runtimeAction, [runtimeAction, runtime](IRuntime* currentRuntime) { runtimeAction->setChecked(runtime == currentRuntime); }); connect(runtime, &QObject::destroyed, this, [this, runtimeAction](QObject* obj) { Q_ASSERT(m_currentRuntime != obj); m_runtimes.removeAll(qobject_cast(obj)); delete runtimeAction; }); m_runtimesMenu->addAction(runtimeAction); } else { connect(runtime, &QObject::destroyed, this, [this](QObject* obj) { Q_ASSERT(m_currentRuntime != obj); m_runtimes.removeAll(qobject_cast(obj)); }); } m_runtimes << runtime; } diff --git a/plugins/custom-definesandincludes/compilerprovider/tests/test_compilerprovider.cpp b/plugins/custom-definesandincludes/compilerprovider/tests/test_compilerprovider.cpp index 230b996bc3..4d5f77b035 100644 --- a/plugins/custom-definesandincludes/compilerprovider/tests/test_compilerprovider.cpp +++ b/plugins/custom-definesandincludes/compilerprovider/tests/test_compilerprovider.cpp @@ -1,254 +1,255 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "test_compilerprovider.h" #include #include +#include #include #include #include #include #include #include #include #include "../compilerprovider.h" #include "../settingsmanager.h" #include "../tests/projectsgenerator.h" using namespace KDevelop; namespace { void testCompilerEntry(SettingsManager* settings, KConfig* config){ auto entries = settings->readPaths(config); auto entry = entries.first(); auto compilers = settings->provider()->compilers(); Q_ASSERT(!compilers.isEmpty()); bool gccCompilerInstalled = std::any_of(compilers.begin(), compilers.end(), [](const CompilerPointer& compiler){return compiler->name().contains(QLatin1String("gcc"), Qt::CaseInsensitive);}); if (gccCompilerInstalled) { QCOMPARE(entry.compiler->name(), QStringLiteral("GCC")); } } void testAddingEntry(SettingsManager* settings, KConfig* config){ auto entries = settings->readPaths(config); auto entry = entries.first(); auto compilers = settings->provider()->compilers(); ConfigEntry otherEntry; otherEntry.defines[QStringLiteral("TEST")] = QStringLiteral("lalal"); otherEntry.includes = QStringList() << QStringLiteral("/foo"); otherEntry.path = QStringLiteral("test"); otherEntry.compiler = compilers.first(); entries << otherEntry; settings->writePaths(config, entries); auto readWriteEntries = settings->readPaths(config); QCOMPARE(readWriteEntries.size(), 2); QCOMPARE(readWriteEntries.at(0).path, entry.path); QCOMPARE(readWriteEntries.at(0).defines, entry.defines); QCOMPARE(readWriteEntries.at(0).includes, entry.includes); QCOMPARE(readWriteEntries.at(0).compiler->name(), entry.compiler->name()); QCOMPARE(readWriteEntries.at(1).path, otherEntry.path); QCOMPARE(readWriteEntries.at(1).defines, otherEntry.defines); QCOMPARE(readWriteEntries.at(1).includes, otherEntry.includes); QCOMPARE(readWriteEntries.at(1).compiler->name(), otherEntry.compiler->name()); } } void TestCompilerProvider::initTestCase() { AutoTestShell::init(); TestCore::initialize(); } void TestCompilerProvider::cleanupTestCase() { TestCore::shutdown(); } void TestCompilerProvider::testRegisterCompiler() { auto settings = SettingsManager::globalInstance(); auto provider = settings->provider(); auto cf = provider->compilerFactories(); for (int i = 0 ; i < cf.size(); ++i) { auto compiler = cf[i]->createCompiler(QString::number(i), QString::number(i)); QVERIFY(provider->registerCompiler(compiler)); QVERIFY(!provider->registerCompiler(compiler)); QVERIFY(provider->compilers().contains(compiler)); } QVERIFY(!provider->registerCompiler({})); } void TestCompilerProvider::testCompilerIncludesAndDefines() { auto settings = SettingsManager::globalInstance(); auto provider = settings->provider(); for (auto c : provider->compilers()) { if (!c->editable() && !c->path().isEmpty()) { QVERIFY(!c->defines(Utils::Cpp, {}).isEmpty()); QVERIFY(!c->includes(Utils::Cpp, {}).isEmpty()); } } QVERIFY(!provider->defines(nullptr).isEmpty()); QVERIFY(!provider->includes(nullptr).isEmpty()); auto compiler = provider->compilerForItem(nullptr); QVERIFY(compiler); QVERIFY(!compiler->defines(Utils::Cpp, QStringLiteral("-std=c++11")).isEmpty()); QVERIFY(!compiler->includes(Utils::Cpp, QStringLiteral("-std=c++11")).isEmpty()); } void TestCompilerProvider::testStorageBackwardsCompatible() { auto settings = SettingsManager::globalInstance(); QTemporaryFile file; QVERIFY(file.open()); QTextStream stream(&file); stream << "[Buildset]\n" << "BuildItems=@Variant(\\x00\\x00\\x00\\t\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x0b\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x1a\\x00S\\x00i\\x00m\\x00p\\x00l\\x00e\\x00P\\x00r\\x00o\\x00j\\x00e\\x00c\\x00t)\n" << "[CustomBuildSystem]\n" << "CurrentConfiguration=BuildConfig0\n" << "[CustomDefinesAndIncludes][ProjectPath0]\n" << "Defines=\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x0c\\x00_\\x00D\\x00E\\x00B\\x00U\\x00G\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x10\\x00V\\x00A\\x00R\\x00I\\x00A\\x00B\\x00L\\x00E\\x00\\x00\\x00\\n\\x00\\x00\\x00\\x00\\n\\x00V\\x00A\\x00L\\x00U\\x00E\n" << "Includes=\\x00\\x00\\x00\\x01\\x00\\x00\\x00$\\x00/\\x00u\\x00s\\x00r\\x00/\\x00i\\x00n\\x00c\\x00l\\x00u\\x00d\\x00e\\x00/\\x00m\\x00y\\x00d\\x00i\\x00r\n" << "Path=/\n" << "[CustomDefinesAndIncludes][ProjectPath0][Compiler]\nName=GCC\nPath=gcc\nType=GCC\n"; file.close(); KConfig config(file.fileName()); auto entries = settings->readPaths(&config); QCOMPARE(entries.size(), 1); auto entry = entries.first(); Defines defines; defines[QStringLiteral("VARIABLE")] = QStringLiteral("VALUE"); defines[QStringLiteral("_DEBUG")] = QString(); QCOMPARE(entry.defines, defines); QStringList includes = QStringList() << QStringLiteral("/usr/include/mydir"); QCOMPARE(entry.includes, includes); QCOMPARE(entry.path, QString("/")); QVERIFY(entry.compiler); testCompilerEntry(settings, &config); testAddingEntry(settings, &config); } void TestCompilerProvider::testStorageNewSystem() { auto settings = SettingsManager::globalInstance(); QTemporaryFile file; QVERIFY(file.open()); QTextStream stream(&file); stream << "[Buildset]\n" << "BuildItems=@Variant(\\x00\\x00\\x00\\t\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x0b\\x00\\x00\\x00\\x00\\x01\\x00\\x00\\x00\\x1a\\x00S\\x00i\\x00m\\x00p\\x00l\\x00e\\x00P\\x00r\\x00o\\x00j\\x00e\\x00c\\x00t)\n\n" << "[CustomBuildSystem]\n" << "CurrentConfiguration=BuildConfig0\n\n" << "[CustomDefinesAndIncludes][ProjectPath0]\n" << "Path=/\n\n" << "[CustomDefinesAndIncludes][ProjectPath0][Defines]\n" << "_DEBUG=\n" << "VARIABLE=VALUE\n" << "[CustomDefinesAndIncludes][ProjectPath0][Includes]\n" << "1=/usr/include/mydir\n" << "2=/usr/local/include/mydir\n" << "[CustomDefinesAndIncludes][ProjectPath0][Compiler]\nName=GCC\nPath=gcc\nType=GCC\n"; file.close(); KConfig config(file.fileName()); auto entries = settings->readPaths(&config); QCOMPARE(entries.size(), 1); auto entry = entries.first(); QCOMPARE(entry.path, QString("/")); Defines defines; defines[QStringLiteral("VARIABLE")] = QStringLiteral("VALUE"); defines[QStringLiteral("_DEBUG")] = QString(); QCOMPARE(entry.defines, defines); QMap includeMap; includeMap[QStringLiteral("1")] = QStringLiteral("/usr/include/mydir"); includeMap[QStringLiteral("2")] = QStringLiteral("/usr/local/include/mydir"); int i = 0; for(auto it = includeMap.begin(); it != includeMap.end(); it++) { QCOMPARE(entry.includes.at(i++), it.value()); } testCompilerEntry(settings, &config); testAddingEntry(settings, &config); } void TestCompilerProvider::testCompilerIncludesAndDefinesForProject() { auto project = ProjectsGenerator::GenerateMultiPathProject(); Q_ASSERT(project); auto settings = SettingsManager::globalInstance(); auto provider = settings->provider(); Q_ASSERT(!provider->compilerFactories().isEmpty()); auto compiler = provider->compilerFactories().first()->createCompiler(QStringLiteral("name"), QStringLiteral("path")); QVERIFY(provider->registerCompiler(compiler)); QVERIFY(provider->compilers().contains(compiler)); auto projectCompiler = provider->compilerForItem(project->projectItem()); QVERIFY(projectCompiler); QVERIFY(projectCompiler != compiler); ProjectBaseItem* mainfile = nullptr; for (const auto& file: project->fileSet() ) { for (auto i: project->filesForPath(file)) { if( i->text() == QLatin1String("main.cpp") ) { mainfile = i; break; } } } QVERIFY(mainfile); auto mainCompiler = provider->compilerForItem(mainfile); QVERIFY(mainCompiler); QVERIFY(mainCompiler->name() == projectCompiler->name()); ConfigEntry entry; entry.path = QStringLiteral("src/main.cpp"); entry.compiler = compiler; auto entries = settings->readPaths(project->projectConfiguration().data()); entries.append(entry); settings->writePaths(project->projectConfiguration().data(), entries); QVERIFY(provider->compilers().contains(compiler)); mainCompiler = provider->compilerForItem(mainfile); QVERIFY(mainCompiler); QVERIFY(mainCompiler->name() == compiler->name()); ICore::self()->projectController()->closeProject(project); } QTEST_MAIN(TestCompilerProvider) diff --git a/plugins/custom-definesandincludes/compilerprovider/widget/compilerswidget.cpp b/plugins/custom-definesandincludes/compilerprovider/widget/compilerswidget.cpp index f4487196c8..530ccd7442 100644 --- a/plugins/custom-definesandincludes/compilerprovider/widget/compilerswidget.cpp +++ b/plugins/custom-definesandincludes/compilerprovider/widget/compilerswidget.cpp @@ -1,251 +1,264 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * */ #include "compilerswidget.h" #include #if KIO_VERSION < QT_VERSION_CHECK(5,21,0) #include #endif #include #include #include #include #include +#include #include "ui_compilerswidget.h" #include "compilersmodel.h" #include "../compilerprovider/settingsmanager.h" #include "../compilerprovider/compilerprovider.h" #include using namespace KDevelop; CompilersWidget::CompilersWidget(QWidget* parent) : ConfigPage(nullptr, nullptr, parent) , m_ui(new Ui::CompilersWidget) , m_compilersModel(new CompilersModel(this)) { m_ui->setupUi(this); m_ui->compilers->setModel(m_compilersModel); m_ui->compilers->header()->setSectionResizeMode(QHeaderView::Stretch); m_ui->addButton->setIcon(QIcon::fromTheme(QStringLiteral("list-add"))); m_ui->removeButton->setIcon(QIcon::fromTheme(QStringLiteral("list-remove"))); m_addMenu = new QMenu(m_ui->addButton); m_mapper = new QSignalMapper(m_addMenu); connect(m_mapper, static_cast(&QSignalMapper::mapped), this, &CompilersWidget::addCompiler); m_addMenu->clear(); auto settings = SettingsManager::globalInstance(); auto provider = settings->provider(); foreach (const auto& factory, provider->compilerFactories()) { QAction* action = new QAction(m_addMenu); action->setText(factory->name()); connect(action, &QAction::triggered, m_mapper, static_cast(&QSignalMapper::map)); m_mapper->setMapping(action, factory->name()); m_addMenu->addAction(action); } m_ui->addButton->setMenu(m_addMenu); connect(m_ui->removeButton, &QPushButton::clicked, this, &CompilersWidget::deleteCompiler); auto delAction = new QAction( i18n("Delete compiler"), this ); delAction->setShortcut( QKeySequence( QStringLiteral("Del") ) ); delAction->setShortcutContext( Qt::WidgetWithChildrenShortcut ); m_ui->compilers->addAction( delAction ); connect( delAction, &QAction::triggered, this, &CompilersWidget::deleteCompiler ); connect(m_ui->compilers->selectionModel(), &QItemSelectionModel::currentChanged, this, &CompilersWidget::compilerSelected); connect(m_ui->compilerName, &QLineEdit::textEdited, this, &CompilersWidget::compilerEdited); #if KIO_VERSION < QT_VERSION_CHECK(5,21,0) // KUrlRequester::textEdited signal only added for 5.21 auto kUrlRequesterLineEdit = m_ui->compilerPath->lineEdit(); Q_ASSERT(kUrlRequesterLineEdit); connect(kUrlRequesterLineEdit, &QLineEdit::textEdited, this, &CompilersWidget::compilerEdited); #else connect(m_ui->compilerPath, &KUrlRequester::textEdited, this, &CompilersWidget::compilerEdited); #endif connect(m_compilersModel, &CompilersModel::compilerChanged, this, &CompilersWidget::compilerChanged); enableItems(false); } CompilersWidget::~CompilersWidget() { } void CompilersWidget::setCompilers(const QVector< CompilerPointer >& compilers) { m_compilersModel->setCompilers(compilers); m_ui->compilers->expandAll(); } void CompilersWidget::clear() { m_compilersModel->setCompilers({}); } void CompilersWidget::deleteCompiler() { qCDebug(DEFINESANDINCLUDES) << "Deleting compiler"; auto selectionModel = m_ui->compilers->selectionModel(); foreach (const QModelIndex& row, selectionModel->selectedIndexes()) { if (row.column() == 1) { //Don't remove the same compiler twice continue; } if(m_compilersModel->removeRows(row.row(), 1, row.parent())) { auto selectedCompiler = selectionModel->selectedIndexes(); compilerSelected(selectedCompiler.isEmpty() ? QModelIndex() : selectedCompiler.first()); } } emit changed(); } void CompilersWidget::addCompiler(const QString& factoryName) { auto settings = SettingsManager::globalInstance(); auto provider = settings->provider(); foreach (const auto& factory, provider->compilerFactories()) { if (factoryName == factory->name()) { //add compiler without any information, the user will fill the data in later auto compilerIndex = m_compilersModel->addCompiler(factory->createCompiler(QString(), QString())); m_ui->compilers->selectionModel()->select(compilerIndex, QItemSelectionModel::Clear | QItemSelectionModel::SelectCurrent | QItemSelectionModel::Rows); compilerSelected(compilerIndex); m_ui->compilers->scrollTo(compilerIndex); m_ui->compilerName->setFocus(Qt::OtherFocusReason); break; } } emit changed(); } QVector< CompilerPointer > CompilersWidget::compilers() const { return m_compilersModel->compilers(); } void CompilersWidget::compilerSelected(const QModelIndex& index) { auto compiler = index.data(CompilersModel::CompilerDataRole); if (compiler.value()) { m_ui->compilerName->setText(compiler.value()->name()); + + //NOTE: there is a bug in kLineEdit, which causes textEdited signal to be + // spuriously emitted on calling setText(). See bug report here: + // https://bugs.kde.org/show_bug.cgi?id=388798 + // The resulting spurious call of compilerEdited then fails with an assert. + //Work around this bug until it is fixed upstream by disabling signals here + const QSignalBlocker blocker(m_ui->compilerPath); m_ui->compilerPath->setText(compiler.value()->path()); + enableItems(true); } else { enableItems(false); } } void CompilersWidget::compilerEdited() { auto indexes = m_ui->compilers->selectionModel()->selectedIndexes(); Q_ASSERT(!indexes.isEmpty()); auto compiler = indexes.first().data(CompilersModel::CompilerDataRole); if (!compiler.value()) { return; } compiler.value()->setName(m_ui->compilerName->text()); compiler.value()->setPath(m_ui->compilerPath->text()); m_compilersModel->updateCompiler(m_ui->compilers->selectionModel()->selection()); emit changed(); } void CompilersWidget::enableItems(bool enable) { m_ui->compilerName->setEnabled(enable); m_ui->compilerPath->setEnabled(enable); if(!enable) { m_ui->compilerName->clear(); + + //NOTE: this is to work around the + //spurious signal bug in kLineEdit + const QSignalBlocker blocker(m_ui->compilerPath); m_ui->compilerPath->clear(); } } void CompilersWidget::reset() { auto settings = SettingsManager::globalInstance(); setCompilers(settings->provider()->compilers()); } void CompilersWidget::apply() { auto settings = SettingsManager::globalInstance(); auto provider = settings->provider(); settings->writeUserDefinedCompilers(compilers()); const auto& providerCompilers = provider->compilers(); const auto& widgetCompilers = compilers(); for (auto compiler: providerCompilers) { if (!widgetCompilers.contains(compiler)) { provider->unregisterCompiler(compiler); } } for (auto compiler: widgetCompilers) { if (!providerCompilers.contains(compiler)) { provider->registerCompiler(compiler); } } } void CompilersWidget::defaults() { } QString CompilersWidget::name() const { return i18n("C/C++ Compilers"); } QString CompilersWidget::fullName() const { return i18n("Configure C/C++ Compilers"); } QIcon CompilersWidget::icon() const { return QIcon::fromTheme(QStringLiteral("kdevelop")); } KDevelop::ConfigPage::ConfigPageType CompilersWidget::configPageType() const { return ConfigPage::LanguageConfigPage; }