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"