File indexing completed on 2024-05-05 04:40:08
0001 /* 0002 SPDX-FileCopyrightText: 2017 Aleix Pol Gonzalez <aleixpol@kde.org> 0003 SPDX-FileCopyrightText: 2018 Daniel Mensinger <daniel@mensinger-ka.de> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "mesonmanager.h" 0009 0010 #include "debug.h" 0011 #include "mesonbuilder.h" 0012 #include "mesonconfig.h" 0013 #include "mintro/mesonintrospectjob.h" 0014 #include "mintro/mesontargets.h" 0015 #include "settings/mesonconfigpage.h" 0016 #include "settings/mesonnewbuilddir.h" 0017 #include "settings/mesonrewriterpage.h" 0018 0019 #include <interfaces/icore.h> 0020 #include <interfaces/iproject.h> 0021 #include <interfaces/iprojectcontroller.h> 0022 #include <interfaces/iruncontroller.h> 0023 #include <interfaces/itestcontroller.h> 0024 #include <project/projectconfigpage.h> 0025 #include <project/projectmodel.h> 0026 #include <util/executecompositejob.h> 0027 0028 #include <KConfigGroup> 0029 #include <KDirWatch> 0030 #include <KLocalizedString> 0031 #include <KPluginFactory> 0032 #include <QFileDialog> 0033 #include <QMessageBox> 0034 #include <QStandardPaths> 0035 0036 #include <algorithm> 0037 0038 using namespace KDevelop; 0039 using namespace std; 0040 0041 static const QString GENERATOR_NINJA = QStringLiteral("ninja"); 0042 0043 K_PLUGIN_FACTORY_WITH_JSON(MesonSupportFactory, "kdevmesonmanager.json", registerPlugin<MesonManager>();) 0044 0045 // ******************************** 0046 // * Error job for failed imports * 0047 // ******************************** 0048 0049 namespace mmanager_internal 0050 { 0051 0052 class ErrorJob : public KJob 0053 { 0054 Q_OBJECT 0055 public: 0056 ErrorJob(QObject* parent, const QString& error) 0057 : KJob(parent) 0058 , m_error(error) 0059 { 0060 } 0061 0062 void start() override 0063 { 0064 QMessageBox::critical(nullptr, i18nc("@title:window", "Project Import Failed"), m_error); 0065 0066 setError(KJob::UserDefinedError + 1); // Indicate that there was an error 0067 setErrorText(m_error); 0068 emitResult(); 0069 } 0070 0071 private: 0072 QString m_error; 0073 }; 0074 0075 } 0076 0077 using namespace mmanager_internal; 0078 0079 // *********************************** 0080 // * Meson specific executable class * 0081 // *********************************** 0082 0083 class MesonProjectExecutableTargetItem final : public ProjectExecutableTargetItem 0084 { 0085 public: 0086 MesonProjectExecutableTargetItem(IProject* project, const QString& name, ProjectBaseItem* parent, Path buildPath, 0087 Path installPath = Path()) 0088 : ProjectExecutableTargetItem(project, name, parent) 0089 , m_buildPath(buildPath) 0090 , m_installPath(installPath) 0091 { 0092 } 0093 0094 QUrl builtUrl() const override { return m_buildPath.toUrl(); } 0095 QUrl installedUrl() const override { return m_installPath.toUrl(); } 0096 0097 private: 0098 Path m_buildPath; 0099 Path m_installPath; 0100 }; 0101 0102 // *************** 0103 // * Constructor * 0104 // *************** 0105 0106 MesonManager::MesonManager(QObject* parent, const QVariantList& args) 0107 : AbstractFileManagerPlugin(QStringLiteral("KDevMesonManager"), parent, args) 0108 , m_builder(new MesonBuilder(this)) 0109 { 0110 if (m_builder->hasError()) { 0111 setErrorDescription(i18n("Meson builder error: %1", m_builder->errorDescription())); 0112 } 0113 } 0114 0115 MesonManager::~MesonManager() 0116 { 0117 delete m_builder; 0118 } 0119 0120 // ********************************* 0121 // * AbstractFileManagerPlugin API * 0122 // ********************************* 0123 0124 IProjectFileManager::Features MesonManager::features() const 0125 { 0126 return IProjectFileManager::Files | IProjectFileManager::Folders | IProjectFileManager::Targets; 0127 } 0128 0129 ProjectFolderItem* MesonManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) 0130 { 0131 // TODO: Maybe use meson targets instead 0132 if (QFile::exists(path.toLocalFile() + QStringLiteral("/meson.build"))) 0133 return new ProjectBuildFolderItem(project, path, parent); 0134 else 0135 return AbstractFileManagerPlugin::createFolderItem(project, path, parent); 0136 } 0137 0138 bool MesonManager::reload(KDevelop::ProjectFolderItem* item) 0139 { 0140 // "Inspired" by CMakeManager::reload 0141 0142 IProject* project = item->project(); 0143 if (!project->isReady()) { 0144 return false; 0145 } 0146 0147 qCDebug(KDEV_Meson) << "reloading meson project" << project->name() << "; Path:" << item->path(); 0148 0149 KJob* job = createImportJob(item); 0150 project->setReloadJob(job); 0151 ICore::self()->runController()->registerJob(job); 0152 if (item == project->projectItem()) { 0153 connect(job, &KJob::finished, this, [project](KJob* job) -> void { 0154 if (job->error()) { 0155 return; 0156 } 0157 0158 emit KDevelop::ICore::self()->projectController()->projectConfigurationChanged(project); 0159 KDevelop::ICore::self()->projectController()->reparseProject(project); 0160 }); 0161 } 0162 0163 return true; 0164 } 0165 0166 // *************************** 0167 // * IBuildSystemManager API * 0168 // *************************** 0169 0170 void MesonManager::populateTargets(ProjectFolderItem* item, QVector<MesonTarget*> targets) 0171 { 0172 // Remove all old targets 0173 for (ProjectTargetItem* i : item->targetList()) { 0174 delete i; 0175 } 0176 0177 // Add the new targets 0178 auto dirPath = item->path(); 0179 for (MesonTarget* i : targets) { 0180 if (!dirPath.isDirectParentOf(i->definedIn())) { 0181 continue; 0182 } 0183 0184 if (i->type().contains(QStringLiteral("executable"))) { 0185 auto outFiles = i->filename(); 0186 Path outFile; 0187 if (outFiles.size() > 0) { 0188 outFile = outFiles[0]; 0189 } 0190 new MesonProjectExecutableTargetItem(item->project(), i->name(), item, outFile); 0191 } else if (i->type().contains(QStringLiteral("library"))) { 0192 new ProjectLibraryTargetItem(item->project(), i->name(), item); 0193 } else { 0194 new ProjectTargetItem(item->project(), i->name(), item); 0195 } 0196 } 0197 0198 // Recurse 0199 for (ProjectFolderItem* i : item->folderList()) { 0200 QVector<MesonTarget*> filteredTargets; 0201 copy_if(begin(targets), end(targets), back_inserter(filteredTargets), 0202 [i](MesonTarget* t) -> bool { return i->path().isParentOf(t->definedIn()); }); 0203 populateTargets(i, filteredTargets); 0204 } 0205 } 0206 0207 QList<ProjectTargetItem*> MesonManager::targets(ProjectFolderItem* item) const 0208 { 0209 Q_ASSERT(item); 0210 QList<ProjectTargetItem*> res = item->targetList(); 0211 for (ProjectFolderItem* i : item->folderList()) { 0212 res << targets(i); 0213 } 0214 return res; 0215 } 0216 0217 void MesonManager::onMesonInfoChanged(QString path, QString projectName) 0218 { 0219 qCDebug(KDEV_Meson) << "File" << path << "changed --> reparsing project"; 0220 IProject* foundProject = ICore::self()->projectController()->findProjectByName(projectName); 0221 if (!foundProject) { 0222 return; 0223 } 0224 0225 KJob* job = createImportJob(foundProject->projectItem()); 0226 foundProject->setReloadJob(job); 0227 ICore::self()->runController()->registerJob(job); 0228 connect(job, &KJob::finished, this, [foundProject](KJob* job) -> void { 0229 if (job->error()) { 0230 return; 0231 } 0232 0233 emit KDevelop::ICore::self()->projectController()->projectConfigurationChanged(foundProject); 0234 KDevelop::ICore::self()->projectController()->reparseProject(foundProject); 0235 }); 0236 } 0237 0238 KJob* MesonManager::createImportJob(ProjectFolderItem* item) 0239 { 0240 IProject* project = item->project(); 0241 Q_ASSERT(project); 0242 0243 qCDebug(KDEV_Meson) << "Importing project" << project->name(); 0244 0245 auto buildDir = Meson::currentBuildDir(project); 0246 0247 KJob* configureJob = nullptr; 0248 if (!buildDir.isValid()) { 0249 configureJob = newBuildDirectory(project, &buildDir); 0250 if (!configureJob) { 0251 QString error = i18n("Importing %1 failed because no build directory could be created.", project->name()); 0252 qCDebug(KDEV_Meson) << error; 0253 return new ErrorJob(this, error); 0254 } 0255 } 0256 0257 auto introJob = new MesonIntrospectJob( 0258 project, buildDir, { MesonIntrospectJob::TARGETS, MesonIntrospectJob::TESTS, MesonIntrospectJob::PROJECTINFO }, 0259 MesonIntrospectJob::BUILD_DIR, this); 0260 0261 KDirWatchPtr watcher = m_projectWatchers[project]; 0262 if (!watcher) { 0263 // Create a new watcher 0264 watcher = m_projectWatchers[project] = make_shared<KDirWatch>(nullptr); 0265 QString projectName = project->name(); 0266 0267 connect(watcher.get(), &KDirWatch::dirty, this, [=](QString p) { onMesonInfoChanged(p, projectName); }); 0268 connect(watcher.get(), &KDirWatch::created, this, [=](QString p) { onMesonInfoChanged(p, projectName); }); 0269 } 0270 0271 Path watchFile = buildDir.buildDir; 0272 watchFile.addPath(QStringLiteral("meson-info")); 0273 watchFile.addPath(QStringLiteral("meson-info.json")); 0274 if (!watcher->contains(watchFile.path())) { 0275 qCDebug(KDEV_Meson) << "Start watching file" << watchFile; 0276 watcher->addFile(watchFile.path()); 0277 } 0278 0279 connect(introJob, &KJob::result, this, [this, introJob, item, project]() { 0280 auto targets = introJob->targets(); 0281 auto tests = introJob->tests(); 0282 if (!targets || !tests) { 0283 return; 0284 } 0285 0286 // Remove old test suites before deleting them 0287 if (m_projectTestSuites[project]) { 0288 for (auto i : m_projectTestSuites[project]->testSuites()) { 0289 ICore::self()->testController()->removeTestSuite(i.get()); 0290 } 0291 } 0292 0293 m_projectTargets[project] = targets; 0294 m_projectTestSuites[project] = tests; 0295 auto tgtList = targets->targets(); 0296 QVector<MesonTarget*> tgtCopy; 0297 tgtCopy.reserve(tgtList.size()); 0298 transform(begin(tgtList), end(tgtList), back_inserter(tgtCopy), [](const auto& a) { return a.get(); }); 0299 0300 populateTargets(item, tgtCopy); 0301 0302 // Add test suites 0303 for (auto& i : tests->testSuites()) { 0304 ICore::self()->testController()->addTestSuite(i.get()); 0305 } 0306 }); 0307 0308 QList<KJob*> jobs; 0309 0310 // Configure the project if necessary 0311 if (!configureJob 0312 && m_builder->evaluateBuildDirectory(buildDir.buildDir, buildDir.mesonBackend) 0313 != MesonBuilder::MESON_CONFIGURED) { 0314 configureJob = builder()->configure(project); 0315 } 0316 0317 if (configureJob) { 0318 jobs << configureJob; 0319 } 0320 0321 jobs << AbstractFileManagerPlugin::createImportJob(item); // generate the file system listing 0322 jobs << introJob; 0323 0324 Q_ASSERT(!jobs.contains(nullptr)); 0325 auto composite = new ExecuteCompositeJob(this, jobs); 0326 composite->setAbortOnSubjobError(false); 0327 return composite; 0328 } 0329 0330 Path MesonManager::buildDirectory(ProjectBaseItem* item) const 0331 { 0332 Q_ASSERT(item); 0333 Meson::BuildDir buildDir = Meson::currentBuildDir(item->project()); 0334 return buildDir.buildDir; 0335 } 0336 0337 IProjectBuilder* MesonManager::builder() const 0338 { 0339 return m_builder; 0340 } 0341 0342 MesonSourcePtr MesonManager::sourceFromItem(KDevelop::ProjectBaseItem* item) const 0343 { 0344 Q_ASSERT(item); 0345 auto it = m_projectTargets.find(item->project()); 0346 if (it == end(m_projectTargets)) { 0347 qCDebug(KDEV_Meson) << item->path().toLocalFile() << "not found"; 0348 return {}; 0349 } 0350 0351 auto targets = *it; 0352 return targets->fileSource(item->path()); 0353 } 0354 0355 KDevelop::Path::List MesonManager::includeDirectories(KDevelop::ProjectBaseItem* item) const 0356 { 0357 auto src = sourceFromItem(item); 0358 if (!src) { 0359 return {}; 0360 } 0361 return src->includeDirs(); 0362 } 0363 0364 KDevelop::Path::List MesonManager::frameworkDirectories(KDevelop::ProjectBaseItem*) const 0365 { 0366 return {}; 0367 } 0368 0369 QHash<QString, QString> MesonManager::defines(KDevelop::ProjectBaseItem* item) const 0370 { 0371 auto src = sourceFromItem(item); 0372 if (!src) { 0373 return {}; 0374 } 0375 return src->defines(); 0376 } 0377 0378 QString MesonManager::extraArguments(KDevelop::ProjectBaseItem* item) const 0379 { 0380 auto src = sourceFromItem(item); 0381 if (!src) { 0382 return {}; 0383 } 0384 return src->extraArgs().join(QLatin1Char(' ')); 0385 } 0386 0387 bool MesonManager::hasBuildInfo(KDevelop::ProjectBaseItem* item) const 0388 { 0389 auto src = sourceFromItem(item); 0390 if (!src) { 0391 return false; 0392 } 0393 return true; 0394 } 0395 0396 KDevelop::Path MesonManager::compiler(KDevelop::ProjectTargetItem* item) const 0397 { 0398 const auto source = sourceFromItem(item); 0399 return source && !source->compiler().isEmpty() ? KDevelop::Path(source->compiler().constFirst()) : KDevelop::Path(); 0400 } 0401 0402 // ******************** 0403 // * Custom functions * 0404 // ******************** 0405 0406 KJob* MesonManager::newBuildDirectory(IProject* project, Meson::BuildDir* outBuildDir) 0407 { 0408 Q_ASSERT(project); 0409 MesonNewBuildDir newBD(project); 0410 0411 if (!newBD.exec() || !newBD.isConfigValid()) { 0412 qCWarning(KDEV_Meson) << "Failed to create new build directory for project " << project->name(); 0413 return nullptr; 0414 } 0415 0416 Meson::BuildDir buildDir = newBD.currentConfig(); 0417 Meson::MesonConfig mesonCfg = Meson::getMesonConfig(project); 0418 buildDir.canonicalizePaths(); 0419 mesonCfg.currentIndex = mesonCfg.addBuildDir(buildDir); 0420 Meson::writeMesonConfig(project, mesonCfg); 0421 0422 if (outBuildDir) { 0423 *outBuildDir = buildDir; 0424 } 0425 0426 return m_builder->configure(project, buildDir, newBD.mesonArgs()); 0427 } 0428 0429 QStringList MesonManager::supportedMesonBackends() const 0430 { 0431 // Maybe add support for other generators 0432 return { GENERATOR_NINJA }; 0433 } 0434 0435 QString MesonManager::defaultMesonBackend() const 0436 { 0437 return GENERATOR_NINJA; 0438 } 0439 0440 Path MesonManager::findMeson() const 0441 { 0442 QString mesonPath; 0443 0444 const static QStringList mesonExecutables = { QStringLiteral("meson"), QStringLiteral("meson.py") }; 0445 const static QStringList mesonPaths 0446 = { QStringLiteral("%1/.local/bin").arg(QStandardPaths::standardLocations(QStandardPaths::HomeLocation)[0]) }; 0447 0448 for (const auto& i : mesonExecutables) { 0449 mesonPath = QStandardPaths::findExecutable(i); 0450 if (!mesonPath.isEmpty()) { 0451 break; 0452 } 0453 0454 mesonPath = QStandardPaths::findExecutable(i, mesonPaths); 0455 if (!mesonPath.isEmpty()) { 0456 break; 0457 } 0458 } 0459 0460 return Path(mesonPath); 0461 } 0462 0463 // *********** 0464 // * IPlugin * 0465 // *********** 0466 0467 ConfigPage* MesonManager::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) 0468 { 0469 switch (number) { 0470 case 0: 0471 return new MesonConfigPage(this, options.project, parent); 0472 case 1: 0473 return new MesonRewriterPage(this, options.project, parent); 0474 } 0475 return nullptr; 0476 } 0477 0478 int MesonManager::perProjectConfigPages() const 0479 { 0480 return 2; 0481 } 0482 0483 #include "mesonmanager.moc" 0484 #include "moc_mesonmanager.cpp"