File indexing completed on 2024-05-05 16:46:01
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 "mesonbuilder.h" 0009 0010 #include "mesonconfig.h" 0011 #include "mesonjob.h" 0012 #include "mesonjobprune.h" 0013 #include "mesonmanager.h" 0014 #include <debug.h> 0015 0016 #include <executecompositejob.h> 0017 #include <interfaces/icore.h> 0018 #include <interfaces/iplugincontroller.h> 0019 #include <interfaces/iproject.h> 0020 #include <outputview/outputexecutejob.h> 0021 #include <project/projectmodel.h> 0022 #include <util/path.h> 0023 0024 #include <KLocalizedString> 0025 0026 #include <QDir> 0027 #include <QFileInfo> 0028 0029 using namespace KDevelop; 0030 0031 class ErrorJob : public OutputJob 0032 { 0033 Q_OBJECT 0034 public: 0035 ErrorJob(QObject* parent, const QString& error) 0036 : OutputJob(parent) 0037 , m_error(error) 0038 { 0039 setStandardToolView(IOutputView::BuildView); 0040 } 0041 0042 void start() override 0043 { 0044 auto* output = new OutputModel(this); 0045 setModel(output); 0046 startOutput(); 0047 0048 output->appendLine(i18n(" *** MESON ERROR ***\n")); 0049 QStringList lines = m_error.split(QLatin1Char('\n')); 0050 output->appendLines(lines); 0051 0052 setError(!m_error.isEmpty()); 0053 setErrorText(m_error); 0054 emitResult(); 0055 } 0056 0057 private: 0058 QString m_error; 0059 }; 0060 0061 MesonBuilder::MesonBuilder(QObject* parent) 0062 : QObject(parent) 0063 { 0064 auto p = KDevelop::ICore::self()->pluginController()->pluginForExtension( 0065 QStringLiteral("org.kdevelop.IProjectBuilder"), QStringLiteral("KDevNinjaBuilder")); 0066 if (p) { 0067 m_ninjaBuilder = p->extension<KDevelop::IProjectBuilder>(); 0068 if (m_ninjaBuilder) { 0069 connect(p, SIGNAL(built(KDevelop::ProjectBaseItem*)), this, SIGNAL(built(KDevelop::ProjectBaseItem*))); 0070 connect(p, SIGNAL(installed(KDevelop::ProjectBaseItem*)), this, 0071 SIGNAL(installed(KDevelop::ProjectBaseItem*))); 0072 connect(p, SIGNAL(cleaned(KDevelop::ProjectBaseItem*)), this, SIGNAL(cleaned(KDevelop::ProjectBaseItem*))); 0073 connect(p, SIGNAL(failed(KDevelop::ProjectBaseItem*)), this, SIGNAL(failed(KDevelop::ProjectBaseItem*))); 0074 } else { 0075 m_errorString = i18n("Failed to set the internally used Ninja builder"); 0076 } 0077 } else { 0078 m_errorString = i18n("Failed to acquire the Ninja builder plugin"); 0079 } 0080 } 0081 0082 MesonBuilder::DirectoryStatus MesonBuilder::evaluateBuildDirectory(const Path& path, const QString& backend) 0083 { 0084 QString pathSTR = path.toLocalFile(); 0085 if (pathSTR.isEmpty()) { 0086 return EMPTY_STRING; 0087 } 0088 0089 QFileInfo info(pathSTR); 0090 if (!info.exists()) { 0091 return DOES_NOT_EXIST; 0092 } 0093 0094 if (!info.isDir() || !info.isReadable() || !info.isWritable()) { 0095 return INVALID_BUILD_DIR; 0096 } 0097 0098 QDir dir(path.toLocalFile()); 0099 if (dir.isEmpty(QDir::NoDotAndDotDot | QDir::Hidden | QDir::AllEntries)) { 0100 return CLEAN; 0101 } 0102 0103 // Check if the directory is a meson directory 0104 const static QStringList mesonPaths = { QStringLiteral("meson-logs"), QStringLiteral("meson-private") }; 0105 for (const auto& i : mesonPaths) { 0106 Path curr = path; 0107 curr.addPath(i); 0108 QFileInfo currFI(curr.toLocalFile()); 0109 if (!currFI.exists()) { 0110 return DIR_NOT_EMPTY; 0111 } 0112 } 0113 0114 // Also check if the meson configuration succeeded. This should be the case if the backend file exists. 0115 // Meson actually checks for meson-private/coredata.dat, this might change in the future. 0116 // see: https://github.com/mesonbuild/meson/blob/master/mesonbuild/msetup.py#L117 0117 QStringList configured = {}; 0118 if (backend == QStringLiteral("ninja")) { 0119 configured << QStringLiteral("build.ninja"); 0120 } 0121 0122 // Check if this is a CONFIGURED meson directory 0123 for (const auto& i : configured) { 0124 Path curr = path; 0125 curr.addPath(i); 0126 QFileInfo currFI(curr.toLocalFile()); 0127 if (!currFI.exists()) { 0128 return MESON_FAILED_CONFIGURATION; 0129 } 0130 } 0131 0132 return MESON_CONFIGURED; 0133 } 0134 0135 KJob* MesonBuilder::configure(IProject* project, const Meson::BuildDir& buildDir, QStringList args, 0136 DirectoryStatus status) 0137 { 0138 Q_ASSERT(project); 0139 0140 if (!buildDir.isValid()) { 0141 return new ErrorJob(this, i18n("The current build directory for %1 is invalid", project->name())); 0142 } 0143 0144 if (status == ___UNDEFINED___) { 0145 status = evaluateBuildDirectory(buildDir.buildDir, buildDir.mesonBackend); 0146 } 0147 0148 KJob* job = nullptr; 0149 0150 switch (status) { 0151 case DOES_NOT_EXIST: 0152 case CLEAN: 0153 case MESON_FAILED_CONFIGURATION: 0154 job = new MesonJob(buildDir, project, MesonJob::CONFIGURE, args, this); 0155 connect(job, &KJob::result, this, [this, project]() { emit configured(project); }); 0156 return job; 0157 case MESON_CONFIGURED: 0158 job = new MesonJob(buildDir, project, MesonJob::RE_CONFIGURE, args, this); 0159 connect(job, &KJob::result, this, [this, project]() { emit configured(project); }); 0160 return job; 0161 case DIR_NOT_EMPTY: 0162 return new ErrorJob( 0163 this, 0164 i18n("The directory '%1' is not empty and does not seem to be an already configured build directory", 0165 buildDir.buildDir.toLocalFile())); 0166 case INVALID_BUILD_DIR: 0167 return new ErrorJob( 0168 this, 0169 i18n("The directory '%1' cannot be used as a meson build directory", buildDir.buildDir.toLocalFile())); 0170 case EMPTY_STRING: 0171 return new ErrorJob( 0172 this, i18n("The current build configuration is broken, because the build directory is not specified")); 0173 default: 0174 // This code should NEVER be reached 0175 return new ErrorJob(this, 0176 i18n("Congratulations: You have reached unreachable code!\n" 0177 "Please report a bug at https://bugs.kde.org/\n" 0178 "FILE: %1:%2", 0179 QStringLiteral(__FILE__), __LINE__)); 0180 } 0181 } 0182 0183 KJob* MesonBuilder::configure(KDevelop::IProject* project) 0184 { 0185 Q_ASSERT(project); 0186 auto buildDir = Meson::currentBuildDir(project); 0187 if (!buildDir.isValid()) { 0188 auto* bsm = project->buildSystemManager(); 0189 auto* manager = dynamic_cast<MesonManager*>(bsm); 0190 if (!manager) { 0191 return new ErrorJob(this, i18n("Internal error: The buildsystem manager is not the MesonManager")); 0192 } 0193 0194 KJob* newBDJob = manager->newBuildDirectory(project); 0195 if (!newBDJob) { 0196 return new ErrorJob(this, i18n("Failed to create a new build directory")); 0197 } 0198 return newBDJob; 0199 } 0200 return configure(project, buildDir, {}); 0201 } 0202 0203 KJob* MesonBuilder::configureIfRequired(KDevelop::IProject* project, KJob* realJob) 0204 { 0205 Q_ASSERT(project); 0206 Meson::BuildDir buildDir = Meson::currentBuildDir(project); 0207 DirectoryStatus status = evaluateBuildDirectory(buildDir.buildDir, buildDir.mesonBackend); 0208 0209 if (status == MESON_CONFIGURED) { 0210 return realJob; 0211 } 0212 0213 KJob* configureJob = nullptr; 0214 if (buildDir.isValid()) { 0215 configureJob = configure(project, buildDir, {}, status); 0216 } else { 0217 // Create a new build directory 0218 auto* bsm = project->buildSystemManager(); 0219 auto* manager = dynamic_cast<MesonManager*>(bsm); 0220 if (!manager) { 0221 return new ErrorJob(this, i18n("Internal error: The buildsystem manager is not the MesonManager")); 0222 } 0223 0224 configureJob = manager->newBuildDirectory(project); 0225 if (!configureJob) { 0226 return new ErrorJob(this, i18n("Failed to create a new build directory")); 0227 } 0228 } 0229 0230 QList<KJob*> jobs = { 0231 configure(project, buildDir, {}, status), // First configure the build directory 0232 realJob // If this succeeds execute the real job 0233 }; 0234 0235 return new ExecuteCompositeJob(this, jobs); 0236 } 0237 0238 KJob* MesonBuilder::build(KDevelop::ProjectBaseItem* item) 0239 { 0240 Q_ASSERT(item); 0241 Q_ASSERT(m_ninjaBuilder); 0242 return configureIfRequired(item->project(), m_ninjaBuilder->build(item)); 0243 } 0244 0245 KJob* MesonBuilder::clean(KDevelop::ProjectBaseItem* item) 0246 { 0247 Q_ASSERT(item); 0248 Q_ASSERT(m_ninjaBuilder); 0249 return configureIfRequired(item->project(), m_ninjaBuilder->clean(item)); 0250 } 0251 0252 KJob* MesonBuilder::install(KDevelop::ProjectBaseItem* item, const QUrl& installPath) 0253 { 0254 Q_ASSERT(item); 0255 Q_ASSERT(m_ninjaBuilder); 0256 return configureIfRequired(item->project(), m_ninjaBuilder->install(item, installPath)); 0257 } 0258 0259 KJob* MesonBuilder::prune(KDevelop::IProject* project) 0260 { 0261 Q_ASSERT(project); 0262 Meson::BuildDir buildDir = Meson::currentBuildDir(project); 0263 if (!buildDir.isValid()) { 0264 qCWarning(KDEV_Meson) << "The current build directory is invalid"; 0265 return new ErrorJob(this, i18n("The current build directory for %1 is invalid", project->name())); 0266 } 0267 0268 KJob* job = new MesonJobPrune(buildDir, this); 0269 connect(job, &KJob::result, this, [this, project]() { emit pruned(project); }); 0270 return job; 0271 } 0272 0273 QList<IProjectBuilder*> MesonBuilder::additionalBuilderPlugins(IProject*) const 0274 { 0275 return { m_ninjaBuilder }; 0276 } 0277 0278 bool MesonBuilder::hasError() const 0279 { 0280 return m_errorString.size() > 0; 0281 } 0282 0283 QString MesonBuilder::errorDescription() const 0284 { 0285 return m_errorString; 0286 } 0287 0288 #include "mesonbuilder.moc" 0289 #include "moc_mesonbuilder.cpp"