File indexing completed on 2024-05-12 04:39:28

0001 /*
0002     SPDX-FileCopyrightText: 2006 Matt Rogers <mattr@kde.org>
0003     SPDX-FileCopyrightText: 2007-2008 Aleix Pol <aleixpol@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "cmakepreferences.h"
0009 
0010 #include <interfaces/icore.h>
0011 
0012 #include <KLocalizedString>
0013 #include <KMessageBox>
0014 #include <KMessageBox_KDevCompat>
0015 #include <KJobWidgets>
0016 #include <KIO/DeleteJob>
0017 
0018 #include <QFile>
0019 #include <QDir>
0020 #include <QHeaderView>
0021 #include <QComboBox>
0022 
0023 #include "ui_cmakebuildsettings.h"
0024 #include "cmakecachedelegate.h"
0025 #include "cmakebuilddirchooser.h"
0026 #include "cmakebuilderconfig.h"
0027 #include <debug.h>
0028 #include <cmakeutils.h>
0029 #include <interfaces/iproject.h>
0030 #include <project/interfaces/ibuildsystemmanager.h>
0031 #include <project/interfaces/iprojectbuilder.h>
0032 #include <interfaces/iruncontroller.h>
0033 
0034 using namespace KDevelop;
0035 
0036 CMakePreferences::CMakePreferences(IPlugin* plugin, const ProjectConfigOptions& options, QWidget* parent)
0037     : ConfigPage(plugin, nullptr, parent), m_project(options.project), m_currentModel(nullptr)
0038 {
0039     m_prefsUi = new Ui::CMakeBuildSettings;
0040     m_prefsUi->setupUi(this);
0041 
0042     m_prefsUi->cacheList->setItemDelegate(new CMakeCacheDelegate(m_prefsUi->cacheList));
0043     m_prefsUi->cacheList->setSelectionMode(QAbstractItemView::SingleSelection);
0044     m_prefsUi->cacheList->horizontalHeader()->setStretchLastSection(true);
0045     m_prefsUi->cacheList->verticalHeader()->hide();
0046 
0047     // configure the extraArguments widget to span the advanced box width but not
0048     // expand the dialog to the width of the longest element in the argument history.
0049     // static_cast<QComboBox*> needed because KComboBox::minimumSizeHint() override by mistake made it protected
0050     // TODO KF6: remove cast, no longer needed
0051     m_prefsUi->extraArguments->setMinimumWidth(static_cast<QComboBox*>(m_prefsUi->extraArguments)->minimumSizeHint().width());
0052     m_extraArgumentsHistory = new CMakeExtraArgumentsHistory(m_prefsUi->extraArguments);
0053 
0054     connect(m_prefsUi->buildDirs, QOverload<int>::of(&KComboBox::currentIndexChanged),
0055             this, &CMakePreferences::buildDirChanged);
0056     connect(m_prefsUi->showInternal, &QCheckBox::stateChanged,
0057             this, &CMakePreferences::showInternal);
0058     connect(m_prefsUi->addBuildDir, &QPushButton::pressed, this, &CMakePreferences::createBuildDir);
0059     connect(m_prefsUi->removeBuildDir, &QPushButton::pressed, this, &CMakePreferences::removeBuildDir);
0060     connect(m_prefsUi->showAdvanced, &QPushButton::toggled, this, &CMakePreferences::showAdvanced);
0061     connect(m_prefsUi->environment, &EnvironmentSelectionWidget::currentProfileChanged,
0062             this, &CMakePreferences::changed);
0063     connect(m_prefsUi->configureEnvironment, &EnvironmentConfigureButton::environmentConfigured,
0064             this, &CMakePreferences::changed);
0065 
0066     connect(m_prefsUi->installationPrefix, &KUrlRequester::textChanged,
0067             this, &CMakePreferences::changed);
0068     connect(m_prefsUi->buildType, &QComboBox::currentTextChanged,
0069             this, &CMakePreferences::changed);
0070     connect(m_prefsUi->extraArguments, &KComboBox::currentTextChanged,
0071             this, &CMakePreferences::changed);
0072     connect(m_prefsUi->extraArguments, &KComboBox::editTextChanged,
0073             this, &CMakePreferences::changed);
0074     connect(m_prefsUi->cMakeExecutable, &KUrlRequester::textChanged,
0075             this, &CMakePreferences::changed);
0076 
0077     showInternal(m_prefsUi->showInternal->checkState());
0078     m_subprojFolder = Path(options.projectTempFile).parent();
0079 
0080     qCDebug(CMAKE) << "Source folder: " << m_srcFolder << options.projectTempFile;
0081 //     foreach(const QVariant &v, args)
0082 //     {
0083 //         qCDebug(CMAKE) << "arg: " << v.toString();
0084 //     }
0085 
0086     m_prefsUi->configureEnvironment->setSelectionWidget(m_prefsUi->environment);
0087 
0088     m_prefsUi->showAdvanced->setChecked(false);
0089     showAdvanced(false);
0090     reset(); // load the initial values
0091 }
0092 
0093 CMakePreferences::~CMakePreferences()
0094 {
0095     CMake::removeOverrideBuildDirIndex(m_project);
0096     delete m_extraArgumentsHistory;
0097     delete m_prefsUi;
0098 }
0099 
0100 void CMakePreferences::initAdvanced()
0101 {
0102     m_prefsUi->environment->setCurrentProfile( CMake::currentEnvironment(m_project) );
0103     m_prefsUi->installationPrefix->setText(CMake::currentInstallDir(m_project).toLocalFile());
0104     m_prefsUi->installationPrefix->setMode(KFile::Directory);
0105     setBuildType(CMake::currentBuildType(m_project));
0106     m_prefsUi->extraArguments->setEditText(CMake::currentExtraArguments(m_project));
0107     m_prefsUi->cMakeExecutable->setText(CMake::currentCMakeExecutable(m_project).toLocalFile());
0108 }
0109 
0110 void CMakePreferences::setBuildType(const QString& buildType)
0111 {
0112     if (m_prefsUi->buildType->currentText() == buildType)
0113         return;
0114 
0115     if (m_prefsUi->buildType->findText(buildType) == -1) {
0116         m_prefsUi->buildType->addItem(buildType);
0117     }
0118     m_prefsUi->buildType->setCurrentIndex(m_prefsUi->buildType->findText(buildType));
0119 }
0120 
0121 void CMakePreferences::reset()
0122 {
0123     qCDebug(CMAKE) << "********loading";
0124     m_prefsUi->buildDirs->clear();
0125     m_prefsUi->buildDirs->addItems( CMake::allBuildDirs(m_project) );
0126     CMake::removeOverrideBuildDirIndex(m_project); // addItems() triggers buildDirChanged(), compensate for it
0127     m_prefsUi->buildDirs->setCurrentIndex( CMake::currentBuildDirIndex(m_project) );
0128 
0129     initAdvanced();
0130 
0131     m_srcFolder = m_project->path();
0132 
0133     m_prefsUi->removeBuildDir->setEnabled(m_prefsUi->buildDirs->count()!=0);
0134 //     QString cmDir=group.readEntry("CMakeDirectory");
0135 //     m_prefsUi->kcfg_cmakeDir->setUrl(QUrl(cmDir));
0136 //     qCDebug(CMAKE) << "cmakedir" << cmDir;
0137 }
0138 
0139 void CMakePreferences::apply()
0140 {
0141     qCDebug(CMAKE) << "*******saving";
0142 
0143     // the build directory list is incrementally maintained through createBuildDir() and removeBuildDir().
0144     // We won't rewrite it here based on the data from m_prefsUi->buildDirs.
0145     CMake::removeOverrideBuildDirIndex( m_project, true ); // save current selection
0146     int savedBuildDir = CMake::currentBuildDirIndex(m_project);
0147     if (savedBuildDir < 0) {
0148         // no build directory exists: skip any writing to config file as well as configuring
0149         return;
0150     }
0151 
0152     CMake::setCurrentEnvironment( m_project, m_prefsUi->environment->currentProfile() );
0153 
0154     CMake::setCurrentInstallDir( m_project, Path(m_prefsUi->installationPrefix->text()) );
0155     const QString buildType = m_prefsUi->buildType->currentText();
0156     CMake::setCurrentBuildType(m_project, buildType);
0157     CMake::setCurrentExtraArguments( m_project, m_prefsUi->extraArguments->currentText() );
0158     CMake::setCurrentCMakeExecutable( m_project, Path(m_prefsUi->cMakeExecutable->text()) );
0159 
0160     qCDebug(CMAKE) << "writing to cmake config: using builddir " << CMake::currentBuildDirIndex(m_project);
0161     qCDebug(CMAKE) << "writing to cmake config: builddir path " << CMake::currentBuildDir(m_project);
0162     qCDebug(CMAKE) << "writing to cmake config: installdir " << CMake::currentInstallDir(m_project);
0163     qCDebug(CMAKE) << "writing to cmake config: build type " << CMake::currentBuildType(m_project);
0164     qCDebug(CMAKE) << "writing to cmake config: cmake executable " << CMake::currentCMakeExecutable(m_project);
0165     qCDebug(CMAKE) << "writing to cmake config: environment " << CMake::currentEnvironment(m_project);
0166 
0167     //We run cmake on the builddir to generate it
0168     configure();
0169 }
0170 
0171 void CMakePreferences::defaults()
0172 {
0173     // do nothing
0174 }
0175 
0176 void CMakePreferences::configureCacheView()
0177 {
0178     // Sets up the cache view after model re-creation/reset.
0179     // Emits changed(false) because model re-creation probably means
0180     // mass programmatical invocation of itemChanged(), which invokes changed(true) - which is not what we want.
0181     m_prefsUi->cacheList->setModel(m_currentModel);
0182     m_prefsUi->cacheList->hideColumn(1);
0183     m_prefsUi->cacheList->hideColumn(3);
0184     m_prefsUi->cacheList->hideColumn(4);
0185     m_prefsUi->cacheList->hideColumn(5);
0186     m_prefsUi->cacheList->horizontalHeader()->resizeSection(0, 200);
0187 
0188     if( m_currentModel ) {
0189         m_prefsUi->cacheList->setEnabled( true );
0190         const auto persistentIndices = m_currentModel->persistentIndices();
0191         for (const QModelIndex& idx : persistentIndices) {
0192             m_prefsUi->cacheList->openPersistentEditor(idx);
0193         }
0194     } else {
0195         m_prefsUi->cacheList->setEnabled( false );
0196     }
0197 
0198     showInternal(m_prefsUi->showInternal->checkState());
0199 }
0200 
0201 void CMakePreferences::updateCache(const Path &newBuildDir)
0202 {
0203     const Path file = newBuildDir.isValid() ? Path(newBuildDir, QStringLiteral("CMakeCache.txt")) : Path();
0204     if(QFile::exists(file.toLocalFile()))
0205     {
0206         if (m_currentModel) {
0207             m_currentModel->deleteLater();
0208         }
0209         m_currentModel = new CMakeCacheModel(this, file);
0210 
0211         configureCacheView();
0212         connect(m_currentModel, &CMakeCacheModel::itemChanged,
0213                 this, &CMakePreferences::cacheEdited);
0214         connect(m_currentModel, &CMakeCacheModel::modelReset,
0215                 this, &CMakePreferences::configureCacheView);
0216         connect(m_prefsUi->cacheList->selectionModel(), &QItemSelectionModel::currentChanged,
0217                 this, &CMakePreferences::listSelectionChanged);
0218         connect(m_currentModel, &CMakeCacheModel::valueChanged, this,
0219                 [this](const QString& name, const QString& value) {
0220                     if (name == QLatin1String("CMAKE_BUILD_TYPE")) {
0221                         setBuildType(value);
0222                     }
0223                 });
0224         connect(m_prefsUi->buildType, &QComboBox::currentTextChanged, m_currentModel, [this](const QString& value) {
0225             if (!m_currentModel)
0226                 return;
0227             const auto items = m_currentModel->findItems(QStringLiteral("CMAKE_BUILD_TYPE"));
0228             for (auto* item : items) {
0229                 m_currentModel->setData(m_currentModel->index(item->row(), 2), value);
0230             }
0231         });
0232     }
0233     else
0234     {
0235         disconnect(m_prefsUi->cacheList->selectionModel(), &QItemSelectionModel::currentChanged, this, nullptr);
0236         if (m_currentModel) {
0237             m_currentModel->deleteLater();
0238             m_currentModel = nullptr;
0239         }
0240         configureCacheView();
0241     }
0242 
0243     if( !m_currentModel )
0244         emit changed();
0245 }
0246 
0247 void CMakePreferences::listSelectionChanged(const QModelIndex & index, const QModelIndex& )
0248 {
0249     qCDebug(CMAKE) << "item " << index << " selected";
0250     QModelIndex idx = index.sibling(index.row(), 3);
0251     QModelIndex idxType = index.sibling(index.row(), 1);
0252     QString comment=QStringLiteral("%1. %2")
0253             .arg(m_currentModel->itemFromIndex(idxType)->text(),
0254                  m_currentModel->itemFromIndex(idx)->text());
0255     m_prefsUi->commentText->setText(comment);
0256 }
0257 
0258 void CMakePreferences::showInternal(int state)
0259 {
0260     if(!m_currentModel)
0261         return;
0262 
0263     bool showAdv=(state == Qt::Checked);
0264     for(int i=0; i<m_currentModel->rowCount(); i++)
0265     {
0266         bool hidden=m_currentModel->isInternal(i) || (!showAdv && m_currentModel->isAdvanced(i));
0267         m_prefsUi->cacheList->setRowHidden(i, hidden);
0268     }
0269 }
0270 
0271 void CMakePreferences::buildDirChanged(int index)
0272 {
0273     CMake::setOverrideBuildDirIndex( m_project, index );
0274     const Path buildDir = CMake::currentBuildDir(m_project);
0275     initAdvanced();
0276     updateCache(buildDir);
0277     qCDebug(CMAKE) << "builddir Changed" << buildDir;
0278     emit changed();
0279 }
0280 
0281 void CMakePreferences::cacheUpdated()
0282 {
0283     const Path buildDir = CMake::currentBuildDir(m_project);
0284     updateCache(buildDir);
0285     qCDebug(CMAKE) << "cache updated for" << buildDir;
0286 }
0287 
0288 void CMakePreferences::createBuildDir()
0289 {
0290     CMakeBuildDirChooser bdCreator;
0291     bdCreator.setProject( m_project );
0292 
0293     // NOTE: (on removing the trailing slashes)
0294     // Generally, we have no clue about how shall a trailing slash look in the current system.
0295     // Moreover, the slash may be a part of the filename.
0296     // It may be '/' or '\', so maybe should we rely on CMake::allBuildDirs() for returning well-formed paths?
0297     QStringList used = CMake::allBuildDirs( m_project );
0298     bdCreator.setAlreadyUsed(used);
0299     bdCreator.setCMakeExecutable(Path(CMakeBuilderSettings::self()->cmakeExecutable().toLocalFile()));
0300 
0301     if(bdCreator.exec())
0302     {
0303         int addedBuildDirIndex = m_prefsUi->buildDirs->count();
0304 
0305         // Initialize the kconfig items with the values from the dialog, this ensures the settings
0306         // end up in the config file once the changes are saved
0307         qCDebug(CMAKE) << "adding to cmake config: new builddir index" << addedBuildDirIndex;
0308         qCDebug(CMAKE) << "adding to cmake config: builddir path " << bdCreator.buildFolder();
0309         qCDebug(CMAKE) << "adding to cmake config: installdir " << bdCreator.installPrefix();
0310         qCDebug(CMAKE) << "adding to cmake config: extra args" << bdCreator.extraArguments();
0311         qCDebug(CMAKE) << "adding to cmake config: build type " << bdCreator.buildType();
0312         qCDebug(CMAKE) << "adding to cmake config: cmake executable " << bdCreator.cmakeExecutable();
0313         qCDebug(CMAKE) << "adding to cmake config: environment empty";
0314         CMake::setOverrideBuildDirIndex( m_project, addedBuildDirIndex );
0315         CMake::setBuildDirCount( m_project, addedBuildDirIndex + 1 );
0316         CMake::setCurrentBuildDir( m_project, bdCreator.buildFolder() );
0317         CMake::setCurrentInstallDir( m_project, bdCreator.installPrefix() );
0318         CMake::setCurrentExtraArguments( m_project, bdCreator.extraArguments() );
0319         CMake::setCurrentBuildType( m_project, bdCreator.buildType() );
0320         CMake::setCurrentCMakeExecutable(m_project, bdCreator.cmakeExecutable());
0321         CMake::setCurrentEnvironment( m_project, QString() );
0322 
0323         QString newbuilddir = bdCreator.buildFolder().toLocalFile();
0324         m_prefsUi->buildDirs->addItem( newbuilddir );
0325         m_prefsUi->buildDirs->setCurrentIndex( addedBuildDirIndex );
0326         m_prefsUi->removeBuildDir->setEnabled( true );
0327 
0328         qCDebug(CMAKE) << "Emitting changed signal for cmake kcm";
0329         emit changed();
0330     }
0331     //TODO: Save it for next runs
0332 }
0333 
0334 void CMakePreferences::removeBuildDir()
0335 {
0336     int curr=m_prefsUi->buildDirs->currentIndex();
0337     if(curr < 0)
0338         return;
0339 
0340     Path removedPath = CMake::currentBuildDir( m_project );
0341     QString removed = removedPath.toLocalFile();
0342     if(QDir(removed).exists())
0343     {
0344         int ret = KMessageBox::warningTwoActions(
0345             this,
0346             i18n("The %1 directory is about to be removed in KDevelop's list.\n"
0347                  "Do you want KDevelop to delete it in the file system as well?",
0348                  removed),
0349             {}, KStandardGuiItem::del(),
0350             KGuiItem(i18nc("@action:button", "Do Not Delete"), QStringLiteral("dialog-cancel")));
0351         if (ret == KMessageBox::PrimaryAction) {
0352             auto deleteJob = KIO::del(removedPath.toUrl());
0353             KJobWidgets::setWindow(deleteJob, this);
0354             if (!deleteJob->exec())
0355                 KMessageBox::error(this, i18n("Could not remove: %1", removed));
0356         }
0357     }
0358 
0359     qCDebug(CMAKE) << "removing from cmake config: using builddir " << curr;
0360     qCDebug(CMAKE) << "removing from cmake config: builddir path " << removedPath;
0361     qCDebug(CMAKE) << "removing from cmake config: installdir " << CMake::currentInstallDir( m_project );
0362     qCDebug(CMAKE) << "removing from cmake config: extra args" << CMake::currentExtraArguments( m_project );
0363     qCDebug(CMAKE) << "removing from cmake config: buildtype " << CMake::currentBuildType( m_project );
0364     qCDebug(CMAKE) << "removing from cmake config: cmake executable " << CMake::currentCMakeExecutable(m_project);
0365     qCDebug(CMAKE) << "removing from cmake config: environment " << CMake::currentEnvironment( m_project );
0366 
0367 
0368     CMake::removeBuildDirConfig(m_project);
0369     m_prefsUi->buildDirs->removeItem( curr ); // this triggers buildDirChanged()
0370     if(m_prefsUi->buildDirs->count()==0)
0371         m_prefsUi->removeBuildDir->setEnabled(false);
0372 
0373     emit changed();
0374 }
0375 
0376 void CMakePreferences::configure()
0377 {
0378     IProjectBuilder *b=m_project->buildSystemManager()->builder();
0379     KJob* job=b->configure(m_project);
0380     if( m_currentModel ) {
0381         QVariantMap map = m_currentModel->changedValues();
0382         job->setProperty("extraCMakeCacheValues", map);
0383         connect(job, &KJob::finished, m_currentModel, &CMakeCacheModel::reset);
0384     } else {
0385         connect(job, &KJob::finished, this, &CMakePreferences::cacheUpdated);
0386     }
0387 
0388     connect(job, &KJob::finished, m_project, &IProject::reloadModel);
0389     ICore::self()->runController()->registerJob(job);
0390 }
0391 
0392 void CMakePreferences::showAdvanced(bool v)
0393 {
0394     qCDebug(CMAKE) << "toggle pressed: " << v;
0395     m_prefsUi->advancedBox->setHidden(!v);
0396 }
0397 
0398 
0399 QString CMakePreferences::name() const
0400 {
0401     return i18nc("@title:tab", "CMake");
0402 }
0403 
0404 QString CMakePreferences::fullName() const
0405 {
0406     return i18nc("@title:tab", "Configure CMake Settings");
0407 }
0408 
0409 QIcon CMakePreferences::icon() const
0410 {
0411     return QIcon::fromTheme(QStringLiteral("cmake"));
0412 }
0413 
0414 #include "moc_cmakepreferences.cpp"