File indexing completed on 2024-05-05 04:40:21
0001 /* 0002 SPDX-FileCopyrightText: 2006 Andreas Pakulat <apaku@gmx.de> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "qmakemanager.h" 0008 0009 #include <QAction> 0010 #include <QDir> 0011 #include <QIcon> 0012 #include <QFileInfo> 0013 #include <QHash> 0014 #include <QList> 0015 #include <QUrl> 0016 0017 #include <KIO/Global> 0018 #include <KConfigGroup> 0019 #include <KDirWatch> 0020 #include <KLocalizedString> 0021 #include <KPluginFactory> 0022 0023 #include <interfaces/icore.h> 0024 #include <interfaces/iproject.h> 0025 #include <interfaces/contextmenuextension.h> 0026 #include <interfaces/context.h> 0027 #include <interfaces/iruncontroller.h> 0028 #include <interfaces/iprojectcontroller.h> 0029 #include <interfaces/iplugincontroller.h> 0030 #include <project/projectmodel.h> 0031 #include <serialization/indexedstring.h> 0032 0033 #include <qmakebuilder/iqmakebuilder.h> 0034 0035 #include "qmakemodelitems.h" 0036 #include "qmakeprojectfile.h" 0037 #include "qmakecache.h" 0038 #include "qmakemkspecs.h" 0039 #include "qmakejob.h" 0040 #include "qmakebuilddirchooserdialog.h" 0041 #include "qmakeconfig.h" 0042 #include "qmakeutils.h" 0043 #include <debug.h> 0044 0045 using namespace KDevelop; 0046 0047 // BEGIN Helpers 0048 0049 QMakeFolderItem* findQMakeFolderParent(ProjectBaseItem* item) 0050 { 0051 QMakeFolderItem* p = nullptr; 0052 while (!p && item) { 0053 p = dynamic_cast<QMakeFolderItem*>(item); 0054 item = item->parent(); 0055 } 0056 return p; 0057 } 0058 0059 // END Helpers 0060 0061 K_PLUGIN_FACTORY_WITH_JSON(QMakeSupportFactory, "kdevqmakemanager.json", registerPlugin<QMakeProjectManager>();) 0062 0063 QMakeProjectManager::QMakeProjectManager(QObject* parent, const QVariantList&) 0064 : AbstractFileManagerPlugin(QStringLiteral("kdevqmakemanager"), parent) 0065 , IBuildSystemManager() 0066 { 0067 IPlugin* i = core()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IQMakeBuilder")); 0068 Q_ASSERT(i); 0069 m_builder = i->extension<IQMakeBuilder>(); 0070 Q_ASSERT(m_builder); 0071 0072 connect(this, SIGNAL(folderAdded(KDevelop::ProjectFolderItem*)), this, 0073 SLOT(slotFolderAdded(KDevelop::ProjectFolderItem*))); 0074 0075 m_runQMake = new QAction(QIcon::fromTheme(QStringLiteral("qtlogo")), i18nc("@action", "Run QMake"), this); 0076 connect(m_runQMake, &QAction::triggered, this, &QMakeProjectManager::slotRunQMake); 0077 } 0078 0079 QMakeProjectManager::~QMakeProjectManager() 0080 { 0081 } 0082 0083 IProjectFileManager::Features QMakeProjectManager::features() const 0084 { 0085 return Features(Folders | Targets | Files); 0086 } 0087 0088 bool QMakeProjectManager::isValid(const Path& path, const bool isFolder, IProject* project) const 0089 { 0090 if (!isFolder && path.lastPathSegment().startsWith(QLatin1String("Makefile"))) { 0091 return false; 0092 } 0093 return AbstractFileManagerPlugin::isValid(path, isFolder, project); 0094 } 0095 0096 Path QMakeProjectManager::buildDirectory(ProjectBaseItem* item) const 0097 { 0098 /// TODO: support includes by some other parent or sibling in a different file-tree-branch 0099 QMakeFolderItem* qmakeItem = findQMakeFolderParent(item); 0100 Path dir; 0101 if (qmakeItem) { 0102 if (!qmakeItem->parent()) { 0103 // build root item 0104 dir = QMakeConfig::buildDirFromSrc(qmakeItem->project(), qmakeItem->path()); 0105 } else { 0106 // build sub-item 0107 const auto proFiles = qmakeItem->projectFiles(); 0108 for (QMakeProjectFile* pro : proFiles) { 0109 if (QDir(pro->absoluteDir()) == QFileInfo(qmakeItem->path().toUrl().toLocalFile() + QLatin1Char('/')).absoluteDir() 0110 || pro->hasSubProject(qmakeItem->path().toUrl().toLocalFile())) { 0111 // get path from project root and it to buildDir 0112 dir = QMakeConfig::buildDirFromSrc(qmakeItem->project(), Path(pro->absoluteDir())); 0113 break; 0114 } 0115 } 0116 } 0117 } 0118 0119 qCDebug(KDEV_QMAKE) << "build dir for" << item->text() << item->path() << "is:" << dir; 0120 return dir; 0121 } 0122 0123 ProjectFolderItem* QMakeProjectManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) 0124 { 0125 if (!parent) { 0126 return projectRootItem(project, path); 0127 } else if (ProjectFolderItem* buildFolder = buildFolderItem(project, path, parent)) { 0128 // child folder in a qmake folder 0129 return buildFolder; 0130 } else { 0131 return AbstractFileManagerPlugin::createFolderItem(project, path, parent); 0132 } 0133 } 0134 0135 ProjectFolderItem* QMakeProjectManager::projectRootItem(IProject* project, const Path& path) 0136 { 0137 QDir dir(path.toLocalFile()); 0138 0139 auto item = new QMakeFolderItem(project, path); 0140 0141 const auto projectfiles = dir.entryList(QStringList() << QStringLiteral("*.pro")); 0142 if (projectfiles.isEmpty()) { 0143 return item; 0144 } 0145 0146 QHash<QString, QString> qmvars = QMakeUtils::queryQMake(project); 0147 const QString mkSpecFile = QMakeConfig::findBasicMkSpec(qmvars); 0148 Q_ASSERT(!mkSpecFile.isEmpty()); 0149 auto* mkspecs = new QMakeMkSpecs(mkSpecFile, qmvars); 0150 mkspecs->setProject(project); 0151 mkspecs->read(); 0152 QMakeCache* cache = findQMakeCache(project); 0153 if (cache) { 0154 cache->setMkSpecs(mkspecs); 0155 cache->read(); 0156 } 0157 0158 for (const auto& projectfile : projectfiles) { 0159 Path proPath(path, projectfile); 0160 /// TODO: use Path in QMakeProjectFile 0161 auto* scope = new QMakeProjectFile(proPath.toLocalFile()); 0162 scope->setProject(project); 0163 scope->setMkSpecs(mkspecs); 0164 scope->setOwnMkSpecs(true); 0165 if (cache) { 0166 scope->setQMakeCache(cache); 0167 } 0168 scope->read(); 0169 qCDebug(KDEV_QMAKE) << "top-level scope with variables:" << scope->variables(); 0170 item->addProjectFile(scope); 0171 } 0172 return item; 0173 } 0174 0175 ProjectFolderItem* QMakeProjectManager::buildFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) 0176 { 0177 // find .pro or .pri files in dir 0178 QDir dir(path.toLocalFile()); 0179 const QStringList projectFiles = dir.entryList(QStringList{QStringLiteral("*.pro"), QStringLiteral("*.pri")}, 0180 QDir::Files); 0181 if (projectFiles.isEmpty()) { 0182 return nullptr; 0183 } 0184 0185 auto folderItem = new QMakeFolderItem(project, path, parent); 0186 0187 // TODO: included by not-parent file (in a nother file-tree-branch). 0188 QMakeFolderItem* qmakeParent = findQMakeFolderParent(parent); 0189 if (!qmakeParent) { 0190 // happens for bad qmake configurations 0191 return nullptr; 0192 } 0193 0194 for (const QString& file : projectFiles) { 0195 const QString absFile = dir.absoluteFilePath(file); 0196 0197 // TODO: multiple includes by different .pro's 0198 QMakeProjectFile* parentPro = nullptr; 0199 const auto proFiles = qmakeParent->projectFiles(); 0200 for (QMakeProjectFile* p : proFiles) { 0201 if (p->hasSubProject(absFile)) { 0202 parentPro = p; 0203 break; 0204 } 0205 } 0206 if (!parentPro && file.endsWith(QLatin1String(".pri"))) { 0207 continue; 0208 } 0209 qCDebug(KDEV_QMAKE) << "add project file:" << absFile; 0210 if (parentPro) { 0211 qCDebug(KDEV_QMAKE) << "parent:" << parentPro->absoluteFile(); 0212 } else { 0213 qCDebug(KDEV_QMAKE) << "no parent, assume project root"; 0214 } 0215 0216 auto qmscope = new QMakeProjectFile(absFile); 0217 qmscope->setProject(project); 0218 0219 const QFileInfo info(absFile); 0220 const QDir d = info.dir(); 0221 /// TODO: cleanup 0222 if (parentPro) { 0223 // subdir 0224 if (QMakeCache* cache = findQMakeCache(project, Path(d.canonicalPath()))) { 0225 cache->setMkSpecs(parentPro->mkSpecs()); 0226 cache->read(); 0227 qmscope->setQMakeCache(cache); 0228 } else { 0229 qmscope->setQMakeCache(parentPro->qmakeCache()); 0230 } 0231 0232 qmscope->setMkSpecs(parentPro->mkSpecs()); 0233 } else { 0234 // new project 0235 auto* root = dynamic_cast<QMakeFolderItem*>(project->projectItem()); 0236 Q_ASSERT(root); 0237 qmscope->setMkSpecs(root->projectFiles().first()->mkSpecs()); 0238 if (root->projectFiles().first()->qmakeCache()) { 0239 qmscope->setQMakeCache(root->projectFiles().first()->qmakeCache()); 0240 } 0241 } 0242 0243 if (qmscope->read()) { 0244 // TODO: only on read? 0245 folderItem->addProjectFile(qmscope); 0246 } else { 0247 delete qmscope; 0248 return nullptr; 0249 } 0250 } 0251 0252 return folderItem; 0253 } 0254 0255 void QMakeProjectManager::slotFolderAdded(ProjectFolderItem* folder) 0256 { 0257 auto* qmakeParent = dynamic_cast<QMakeFolderItem*>(folder); 0258 if (!qmakeParent) { 0259 return; 0260 } 0261 0262 qCDebug(KDEV_QMAKE) << "adding targets for" << folder->path(); 0263 const auto proFiles = qmakeParent->projectFiles(); 0264 for (QMakeProjectFile* pro : proFiles) { 0265 const auto targets = pro->targets(); 0266 for (const auto& s : targets) { 0267 if (!isValid(Path(folder->path(), s), false, folder->project())) { 0268 continue; 0269 } 0270 qCDebug(KDEV_QMAKE) << "adding target:" << s; 0271 Q_ASSERT(!s.isEmpty()); 0272 auto target = new QMakeTargetItem(pro, folder->project(), s, folder); 0273 const auto files = pro->filesForTarget(s); 0274 for (const auto& path : files) { 0275 new ProjectFileItem(folder->project(), Path(path), target); 0276 /// TODO: signal? 0277 } 0278 } 0279 } 0280 } 0281 0282 ProjectFolderItem* QMakeProjectManager::import(IProject* project) 0283 { 0284 const Path dirName = project->path(); 0285 if (dirName.isRemote()) { 0286 // FIXME turn this into a real warning 0287 qCWarning(KDEV_QMAKE) << "not a local file. QMake support doesn't handle remote projects"; 0288 return nullptr; 0289 } 0290 0291 QMakeUtils::checkForNeedingConfigure(project); 0292 0293 ProjectFolderItem* ret = AbstractFileManagerPlugin::import(project); 0294 connect(projectWatcher(project), &KDirWatch::dirty, this, &QMakeProjectManager::slotDirty); 0295 return ret; 0296 } 0297 0298 void QMakeProjectManager::slotDirty(const QString& path) 0299 { 0300 if (!path.endsWith(QLatin1String(".pro")) && !path.endsWith(QLatin1String(".pri"))) { 0301 return; 0302 } 0303 0304 QFileInfo info(path); 0305 if (!info.isFile()) { 0306 return; 0307 } 0308 0309 const QUrl url = QUrl::fromLocalFile(path); 0310 if (!isValid(Path(url), false, nullptr)) { 0311 return; 0312 } 0313 0314 IProject* project = ICore::self()->projectController()->findProjectForUrl(url); 0315 if (!project) { 0316 // this can happen when we create/remove lots of files in a 0317 // sub dir of a project - ignore such cases for now 0318 return; 0319 } 0320 0321 bool finished = false; 0322 const auto folders = project->foldersForPath(IndexedString(KIO::upUrl(url))); 0323 for (ProjectFolderItem* folder : folders) { 0324 if (auto* qmakeFolder = dynamic_cast<QMakeFolderItem*>(folder)) { 0325 const auto proFiles = qmakeFolder->projectFiles(); 0326 for (QMakeProjectFile* pro : proFiles) { 0327 if (pro->absoluteFile() == path) { 0328 // TODO: children 0329 // TODO: cache added 0330 qCDebug(KDEV_QMAKE) << "reloading" << pro << path; 0331 pro->read(); 0332 } 0333 } 0334 finished = true; 0335 } else if (ProjectFolderItem* newFolder = buildFolderItem(project, folder->path(), folder->parent())) { 0336 qCDebug(KDEV_QMAKE) << "changing from normal folder to qmake project folder:" << folder->path().toUrl(); 0337 // .pro / .pri file did not exist before 0338 while (folder->rowCount()) { 0339 newFolder->appendRow(folder->takeRow(0)); 0340 } 0341 folder->parent()->removeRow(folder->row()); 0342 folder = newFolder; 0343 finished = true; 0344 } 0345 if (finished) { 0346 // remove existing targets and readd them 0347 for (int i = 0; i < folder->rowCount(); ++i) { 0348 if (folder->child(i)->target()) { 0349 folder->removeRow(i); 0350 } 0351 } 0352 /// TODO: put into it's own function once we add more stuff to that slot 0353 slotFolderAdded(folder); 0354 break; 0355 } 0356 } 0357 } 0358 0359 QList<ProjectTargetItem*> QMakeProjectManager::targets(ProjectFolderItem* item) const 0360 { 0361 Q_UNUSED(item) 0362 return QList<ProjectTargetItem*>(); 0363 } 0364 0365 IProjectBuilder* QMakeProjectManager::builder() const 0366 { 0367 Q_ASSERT(m_builder); 0368 return m_builder; 0369 } 0370 0371 Path::List QMakeProjectManager::collectDirectories(ProjectBaseItem* item, const bool collectIncludes) const 0372 { 0373 Path::List list; 0374 QMakeFolderItem* folder = findQMakeFolderParent(item); 0375 if (folder) { 0376 const auto proFiles = folder->projectFiles(); 0377 for (QMakeProjectFile* pro : proFiles) { 0378 if (pro->files().contains(item->path().toLocalFile())) { 0379 const QStringList directories = collectIncludes ? pro->includeDirectories() : pro->frameworkDirectories(); 0380 for (const QString& dir : directories) { 0381 Path path(dir); 0382 if (!list.contains(path)) { 0383 list << path; 0384 } 0385 } 0386 } 0387 } 0388 if (list.isEmpty()) { 0389 // fallback for new files, use all possible include dirs 0390 const auto proFiles = folder->projectFiles(); 0391 for (QMakeProjectFile* pro : proFiles) { 0392 const QStringList directories = collectIncludes ? pro->includeDirectories() : pro->frameworkDirectories(); 0393 for (const QString& dir : directories) { 0394 Path path(dir); 0395 if (!list.contains(path)) { 0396 list << path; 0397 } 0398 } 0399 } 0400 } 0401 // make sure the base dir is included 0402 if (!list.contains(folder->path())) { 0403 list << folder->path(); 0404 } 0405 // qCDebug(KDEV_QMAKE) << "include dirs for" << item->path() << ":" << list; 0406 } 0407 return list; 0408 } 0409 0410 Path::List QMakeProjectManager::includeDirectories(ProjectBaseItem* item) const 0411 { 0412 return collectDirectories(item); 0413 } 0414 0415 Path::List QMakeProjectManager::frameworkDirectories(ProjectBaseItem* item) const 0416 { 0417 return collectDirectories(item, false); 0418 } 0419 0420 QHash<QString, QString> QMakeProjectManager::defines(ProjectBaseItem* item) const 0421 { 0422 QHash<QString, QString> d; 0423 QMakeFolderItem* folder = findQMakeFolderParent(item); 0424 if (!folder) { 0425 // happens for bad qmake configurations 0426 return d; 0427 } 0428 const auto proFiles = folder->projectFiles(); 0429 for (QMakeProjectFile* pro : proFiles) { 0430 const auto defines = pro->defines(); 0431 for (const auto& def : defines) { 0432 d.insert(def.first, def.second); 0433 } 0434 } 0435 return d; 0436 } 0437 0438 QString QMakeProjectManager::extraArguments(KDevelop::ProjectBaseItem *item) const 0439 { 0440 QMakeFolderItem* folder = findQMakeFolderParent(item); 0441 if (!folder) { 0442 // happens for bad qmake configurations 0443 return {}; 0444 } 0445 0446 QStringList d; 0447 const auto proFiles = folder->projectFiles(); 0448 for (QMakeProjectFile* pro : proFiles) { 0449 d << pro->extraArguments(); 0450 } 0451 return d.join(QLatin1Char(' ')); 0452 } 0453 0454 bool QMakeProjectManager::hasBuildInfo(KDevelop::ProjectBaseItem* item) const 0455 { 0456 return findQMakeFolderParent(item); 0457 } 0458 0459 QMakeCache* QMakeProjectManager::findQMakeCache(IProject* project, const Path& path) const 0460 { 0461 QDir curdir(QMakeConfig::buildDirFromSrc(project, !path.isValid() ? project->path() : path).toLocalFile()); 0462 curdir.makeAbsolute(); 0463 while (!curdir.exists(QStringLiteral(".qmake.cache")) && !curdir.isRoot() && curdir.cdUp()) { 0464 qCDebug(KDEV_QMAKE) << curdir; 0465 } 0466 0467 if (curdir.exists(QStringLiteral(".qmake.cache"))) { 0468 qCDebug(KDEV_QMAKE) << "Found QMake cache in " << curdir.absolutePath(); 0469 return new QMakeCache(curdir.canonicalPath() + QLatin1String("/.qmake.cache")); 0470 } 0471 return nullptr; 0472 } 0473 0474 ContextMenuExtension QMakeProjectManager::contextMenuExtension(Context* context, QWidget* parent) 0475 { 0476 Q_UNUSED(parent); 0477 0478 ContextMenuExtension ext; 0479 0480 if (context->hasType(Context::ProjectItemContext)) { 0481 auto* pic = dynamic_cast<ProjectItemContext*>(context); 0482 Q_ASSERT(pic); 0483 if (pic->items().isEmpty()) { 0484 return ext; 0485 } 0486 0487 m_actionItem = dynamic_cast<QMakeFolderItem*>(pic->items().first()); 0488 if (m_actionItem) { 0489 ext.addAction(ContextMenuExtension::ProjectGroup, m_runQMake); 0490 } 0491 } 0492 0493 return ext; 0494 } 0495 0496 void QMakeProjectManager::slotRunQMake() 0497 { 0498 Q_ASSERT(m_actionItem); 0499 0500 Path srcDir = m_actionItem->path(); 0501 Path buildDir = QMakeConfig::buildDirFromSrc(m_actionItem->project(), srcDir); 0502 auto* job = new QMakeJob(srcDir.toLocalFile(), buildDir.toLocalFile(), this); 0503 0504 job->setQMakePath(QMakeConfig::qmakeExecutable(m_actionItem->project())); 0505 0506 KConfigGroup cg(m_actionItem->project()->projectConfiguration(), QMakeConfig::CONFIG_GROUP); 0507 QString installPrefix = cg.readEntry(QMakeConfig::INSTALL_PREFIX, QString()); 0508 if (!installPrefix.isEmpty()) 0509 job->setInstallPrefix(installPrefix); 0510 job->setBuildType(cg.readEntry<int>(QMakeConfig::BUILD_TYPE, 0)); 0511 job->setExtraArguments(cg.readEntry(QMakeConfig::EXTRA_ARGUMENTS, QString())); 0512 0513 KDevelop::ICore::self()->runController()->registerJob(job); 0514 } 0515 0516 KDevelop::Path QMakeProjectManager::compiler(KDevelop::ProjectTargetItem* p) const 0517 { 0518 Q_UNUSED(p); 0519 return {}; 0520 } 0521 0522 #include "qmakemanager.moc" 0523 #include "moc_qmakemanager.cpp"