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"