File indexing completed on 2024-05-05 04:39:24

0001 /*
0002     SPDX-FileCopyrightText: 2007 Aleix Pol <aleixpol@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "cmakebuilddirchooser.h"
0008 #include "ui_cmakebuilddirchooser.h"
0009 #include "cmakeutils.h"
0010 #include "debug.h"
0011 
0012 #include <project/helper.h>
0013 #include <interfaces/iproject.h>
0014 #include <interfaces/icore.h>
0015 #include <interfaces/iruntime.h>
0016 #include <interfaces/iruntimecontroller.h>
0017 
0018 #include <KLocalizedString>
0019 
0020 #include <QDir>
0021 #include <QDialogButtonBox>
0022 #include <QVBoxLayout>
0023 
0024 using namespace KDevelop;
0025 
0026 CMakeBuildDirChooser::CMakeBuildDirChooser(QWidget* parent)
0027     : QDialog(parent)
0028 {
0029     setWindowTitle(i18nc("@title:window", "Configure a Build Directory - %1", ICore::self()->runtimeController()->currentRuntime()->name()));
0030 
0031     m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0032     m_buttonBox->button(QDialogButtonBox::Ok)->setDefault(true);
0033     connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
0034     connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
0035 
0036     auto mainWidget = new QWidget(this);
0037     auto mainLayout = new QVBoxLayout;
0038     setLayout(mainLayout);
0039     mainLayout->addWidget(mainWidget);
0040 
0041     m_chooserUi = new Ui::CMakeBuildDirChooser;
0042     m_chooserUi->setupUi(mainWidget);
0043     setShowAvailableBuildDirs(false);
0044     mainLayout->addWidget(m_buttonBox);
0045 
0046     m_chooserUi->buildFolder->setMode(KFile::Directory|KFile::ExistingOnly);
0047     m_chooserUi->installPrefix->setMode(KFile::Directory|KFile::ExistingOnly);
0048 
0049     // configure the extraArguments widget to span the widget width but not
0050     // expand the dialog to the width of the longest element in the argument history.
0051     // static_cast<QComboBox*> needed because KComboBox::minimumSizeHint() override by mistake made it protected
0052     // TODO KF6: remove cast, no longer needed
0053     m_chooserUi->extraArguments->setMinimumWidth(static_cast<QComboBox*>(m_chooserUi->extraArguments)->minimumSizeHint().width());
0054     m_extraArgumentsHistory = new CMakeExtraArgumentsHistory(m_chooserUi->extraArguments);
0055 
0056     connect(m_chooserUi->buildFolder, &KUrlRequester::textChanged, this, &CMakeBuildDirChooser::updated);
0057     connect(m_chooserUi->buildType, &QComboBox::currentTextChanged,
0058             this, &CMakeBuildDirChooser::updated);
0059     connect(m_chooserUi->extraArguments, &KComboBox::editTextChanged, this, &CMakeBuildDirChooser::updated);
0060     connect(m_chooserUi->availableBuildDirs, QOverload<int>::of(&QComboBox::currentIndexChanged),
0061             this, &CMakeBuildDirChooser::adoptPreviousBuildDirectory);
0062 
0063     const auto defaultInstallPrefix = ICore::self()->runtimeController()->currentRuntime()->getenv("KDEV_DEFAULT_INSTALL_PREFIX");
0064     if (!defaultInstallPrefix.isEmpty()) {
0065         m_chooserUi->installPrefix->setUrl(QUrl::fromLocalFile(QFile::decodeName(defaultInstallPrefix)));
0066     }
0067 
0068     updated();
0069 }
0070 
0071 CMakeBuildDirChooser::~CMakeBuildDirChooser()
0072 {
0073     delete m_extraArgumentsHistory;
0074 
0075     delete m_chooserUi;
0076 }
0077 
0078 void CMakeBuildDirChooser::setProject( IProject* project )
0079 {
0080     m_project = project;
0081 
0082     KDevelop::Path folder = m_project->path();
0083     QString relative=CMake::projectRootRelative(m_project);
0084     folder.cd(relative);
0085     m_srcFolder = folder;
0086 
0087     m_chooserUi->buildFolder->setUrl(KDevelop::proposedBuildFolder(m_srcFolder).toUrl());
0088     setWindowTitle(i18nc("@title:window", "Configure a Build Directory for %1", project->name()));
0089     update();
0090 }
0091 
0092 void CMakeBuildDirChooser::buildDirSettings(
0093     const KDevelop::Path& buildDir,
0094     QString& srcDir,
0095     QString& installDir,
0096     QString& buildType)
0097 {
0098     const QByteArray srcLine = QByteArrayLiteral("CMAKE_HOME_DIRECTORY:INTERNAL=");
0099     const QByteArray installLine = QByteArrayLiteral("CMAKE_INSTALL_PREFIX:PATH=");
0100     const QByteArray buildLine = QByteArrayLiteral("CMAKE_BUILD_TYPE:STRING=");
0101 
0102     const Path cachePath(buildDir, QStringLiteral("CMakeCache.txt"));
0103     QFile file(cachePath.toLocalFile());
0104 
0105     srcDir.clear();
0106     installDir.clear();
0107     buildType.clear();
0108 
0109     if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
0110     {
0111         qCWarning(CMAKE) << "Something really strange happened reading" << cachePath;
0112         return;
0113     }
0114 
0115     int cnt = 0;
0116     while (cnt != 3 && !file.atEnd())
0117     {
0118         auto rawLine = file.readLine();
0119 
0120         if (rawLine.endsWith('\n'))
0121             rawLine.chop(1);
0122         if (rawLine.endsWith('\r'))
0123             rawLine.chop(1);
0124 
0125         auto match = [&rawLine](const QByteArray& prefix, QString* target) -> bool
0126         {
0127             if (rawLine.startsWith(prefix)) {
0128                 // note: CMakeCache.txt is UTF8-encoded, also see bug 329305
0129                 *target = QString::fromUtf8(rawLine.constData() + prefix.size(), rawLine.size() - prefix.size());
0130                 return true;
0131             }
0132             return false;
0133         };
0134 
0135         if (match(srcLine, &srcDir) || match(installLine, &installDir) || match(buildLine, &buildType)) {
0136             ++cnt;
0137         }
0138     }
0139 
0140     qCDebug(CMAKE) << "The source directory for " << file.fileName() << "is" << srcDir;
0141     qCDebug(CMAKE) << "The install directory for " << file.fileName() << "is" << installDir;
0142     qCDebug(CMAKE) << "The build type for " << file.fileName() << "is" << buildType;
0143 }
0144 
0145 void CMakeBuildDirChooser::updated()
0146 {
0147     StatusTypes st;
0148     Path chosenBuildFolder(m_chooserUi->buildFolder->url());
0149     bool emptyUrl = chosenBuildFolder.isEmpty();
0150     if( emptyUrl ) st |= BuildFolderEmpty;
0151 
0152     bool dirEmpty = false, dirExists= false, dirRelative = false;
0153     QString srcDir;
0154     QString installDir;
0155     QString buildType;
0156     if(!emptyUrl)
0157     {
0158         QDir d(chosenBuildFolder.toLocalFile());
0159         dirExists = d.exists();
0160         dirEmpty = dirExists && d.count()<=2;
0161         dirRelative = d.isRelative();
0162         if(!dirEmpty && dirExists && !dirRelative)
0163         {
0164             bool hasCache=QFile::exists(Path(chosenBuildFolder, QStringLiteral("CMakeCache.txt")).toLocalFile());
0165             if(hasCache)
0166             {
0167                 QString proposed=m_srcFolder.toLocalFile();
0168 
0169                 buildDirSettings(chosenBuildFolder, srcDir, installDir, buildType);
0170                 if(!srcDir.isEmpty())
0171                 {
0172                     auto rt = ICore::self()->runtimeController()->currentRuntime();
0173                     if(QDir(rt->pathInHost(Path(srcDir)).toLocalFile()).canonicalPath() == QDir(proposed).canonicalPath())
0174                     {
0175                             st |= CorrectBuildDir | BuildDirCreated;
0176                     }
0177                 }
0178                 else
0179                 {
0180                     qCWarning(CMAKE) << "maybe you are trying a damaged CMakeCache.txt file. Proper: ";
0181                 }
0182 
0183                 if(!installDir.isEmpty() && QDir(installDir).exists())
0184                 {
0185                     m_chooserUi->installPrefix->setUrl(QUrl::fromLocalFile(installDir));
0186                 }
0187 
0188                 m_chooserUi->buildType->setCurrentText(buildType);
0189             }
0190         }
0191 
0192         if(m_alreadyUsed.contains(chosenBuildFolder.toLocalFile()) && !m_chooserUi->availableBuildDirs->isEnabled()) {
0193             st=DirAlreadyCreated;
0194         }
0195     }
0196     else
0197     {
0198         setStatus(i18n("You need to specify a build directory."), false);
0199         return;
0200     }
0201 
0202 
0203     if(st & (BuildDirCreated | CorrectBuildDir))
0204     {
0205         setStatus(i18n("Using an already created build directory."), true);
0206         m_chooserUi->installPrefix->setEnabled(false);
0207         m_chooserUi->buildType->setEnabled(false);
0208     }
0209     else
0210     {
0211         bool correct = (dirEmpty || !dirExists) && !(st & DirAlreadyCreated) && !dirRelative;
0212 
0213         if(correct)
0214         {
0215             st |= CorrectBuildDir;
0216             setStatus(i18n("Creating a new build directory."), true);
0217         }
0218         else
0219         {
0220             //Useful to explain what's going wrong
0221             if(st & DirAlreadyCreated)
0222                 setStatus(i18n("Build directory already configured."), false);
0223             else if (!srcDir.isEmpty())
0224                 setStatus(i18n("This build directory is for %1, "
0225                                "but the project directory is %2.", srcDir, m_srcFolder.toLocalFile()), false);
0226             else if(dirRelative)
0227                 setStatus(i18n("You may not select a relative build directory."), false);
0228             else if(!dirEmpty)
0229                 setStatus(i18n("The selected build directory is not empty."), false);
0230         }
0231 
0232         m_chooserUi->installPrefix->setEnabled(correct);
0233         m_chooserUi->buildType->setEnabled(correct);
0234     }
0235 }
0236 
0237 void CMakeBuildDirChooser::setCMakeExecutable(const Path& path)
0238 {
0239     m_chooserUi->cmakeExecutable->setUrl(path.toUrl());
0240     updated();
0241 }
0242 
0243 void CMakeBuildDirChooser::setInstallPrefix(const Path& path)
0244 {
0245     m_chooserUi->installPrefix->setUrl(path.toUrl());
0246     updated();
0247 }
0248 
0249 void CMakeBuildDirChooser::setBuildFolder(const Path& path)
0250 {
0251     m_chooserUi->buildFolder->setUrl(path.toUrl());
0252     updated();
0253 }
0254 
0255 void CMakeBuildDirChooser::setBuildType(const QString& s)
0256 {
0257     m_chooserUi->buildType->addItem(s);
0258     m_chooserUi->buildType->setCurrentIndex(m_chooserUi->buildType->findText(s));
0259     updated();
0260 }
0261 
0262 void CMakeBuildDirChooser::setAlreadyUsed (const QStringList & used)
0263 {
0264     m_chooserUi->availableBuildDirs->addItems(used);
0265     m_alreadyUsed = used;
0266     updated();
0267 }
0268 
0269 void CMakeBuildDirChooser::setExtraArguments(const QString& args)
0270 {
0271     m_chooserUi->extraArguments->setEditText(args);
0272     updated();
0273 }
0274 
0275 void CMakeBuildDirChooser::setStatus(const QString& message, bool canApply)
0276 {
0277     m_chooserUi->status->setMessageType(canApply ? KMessageWidget::Positive : KMessageWidget::Warning);
0278     m_chooserUi->status->setText(message);
0279 
0280     auto okButton = m_buttonBox->button(QDialogButtonBox::Ok);
0281     okButton->setEnabled(canApply);
0282     if (canApply) {
0283         auto cancelButton = m_buttonBox->button(QDialogButtonBox::Cancel);
0284         cancelButton->clearFocus();
0285     }
0286 }
0287 
0288 void CMakeBuildDirChooser::adoptPreviousBuildDirectory(int index)
0289 {
0290     if (index > 0) {
0291         Q_ASSERT(m_project);
0292         m_chooserUi->cmakeExecutable->setUrl(CMake::currentCMakeExecutable(m_project, index -1).toUrl());
0293         m_chooserUi->buildFolder->setUrl(CMake::currentBuildDir(m_project, index -1).toUrl());
0294         m_chooserUi->installPrefix->setUrl(CMake::currentInstallDir(m_project, index -1).toUrl());
0295         m_chooserUi->buildType->setCurrentText(CMake::currentBuildType(m_project, index -1));
0296         m_chooserUi->extraArguments->setCurrentText(CMake::currentExtraArguments(m_project, index -1));
0297     }
0298 
0299     m_chooserUi->label_5->setEnabled(index == 0);
0300     m_chooserUi->cmakeExecutable->setEnabled(index == 0);
0301     m_chooserUi->label_3->setEnabled(index == 0);
0302     m_chooserUi->buildFolder->setEnabled(index == 0);
0303     m_chooserUi->label->setEnabled(index == 0);
0304     m_chooserUi->installPrefix->setEnabled(index == 0);
0305     m_chooserUi->label_2->setEnabled(index == 0);
0306     m_chooserUi->buildType->setEnabled(index == 0);
0307     m_chooserUi->status->setEnabled(index == 0);
0308     m_chooserUi->extraArguments->setEnabled(index == 0);
0309     m_chooserUi->label_4->setEnabled(index == 0);
0310 }
0311 
0312 bool CMakeBuildDirChooser::reuseBuilddir()
0313 {
0314     return m_chooserUi->availableBuildDirs->currentIndex() > 0;
0315 }
0316 
0317 int CMakeBuildDirChooser::alreadyUsedIndex() const
0318 {
0319     return m_chooserUi->availableBuildDirs->currentIndex() - 1;
0320 }
0321 
0322 void CMakeBuildDirChooser::setShowAvailableBuildDirs(bool show)
0323 {
0324     m_chooserUi->availableLabel->setVisible(show);
0325     m_chooserUi->availableBuildDirs->setVisible(show);
0326 }
0327 
0328 Path CMakeBuildDirChooser::cmakeExecutable() const { return Path(m_chooserUi->cmakeExecutable->url()); }
0329 
0330 Path CMakeBuildDirChooser::installPrefix() const { return Path(m_chooserUi->installPrefix->url()); }
0331 
0332 Path CMakeBuildDirChooser::buildFolder() const { return Path(m_chooserUi->buildFolder->url()); }
0333 
0334 QString CMakeBuildDirChooser::buildType() const { return m_chooserUi->buildType->currentText(); }
0335 
0336 QString CMakeBuildDirChooser::extraArguments() const { return m_chooserUi->extraArguments->currentText(); }
0337 
0338 #include "moc_cmakebuilddirchooser.cpp"