File indexing completed on 2024-05-05 04:40:08

0001 /*
0002     SPDX-FileCopyrightText: 2012 Aleix Pol Gonzalez <aleixpol@kde.org>
0003     SPDX-FileCopyrightText: 2017 Kevin Funk <kfunk@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-only
0006 */
0007 
0008 #include "ninjabuilder.h"
0009 
0010 #include "ninjajob.h"
0011 #include "ninjabuilderpreferences.h"
0012 #include <debug.h>
0013 
0014 #include <KPluginFactory>
0015 #include <KConfigGroup>
0016 #include <KShell>
0017 #include <project/projectmodel.h>
0018 #include <project/interfaces/ibuildsystemmanager.h>
0019 #include <project/builderjob.h>
0020 #include <interfaces/iproject.h>
0021 
0022 #include <QFile>
0023 
0024 K_PLUGIN_FACTORY_WITH_JSON(NinjaBuilderFactory, "kdevninja.json", registerPlugin<NinjaBuilder>(); )
0025 
0026 NinjaBuilder::NinjaBuilder(QObject* parent, const QVariantList&)
0027     : KDevelop::IPlugin(QStringLiteral("kdevninja"), parent)
0028 {
0029     if (NinjaJob::ninjaExecutable().isEmpty()) {
0030         setErrorDescription(i18n("Unable to find ninja executable. Is it installed on the system?"));
0031     }
0032 }
0033 
0034 static QStringList targetsInFolder(KDevelop::ProjectFolderItem* item)
0035 {
0036     QStringList ret;
0037     const auto targets = item->targetList();
0038     ret.reserve(targets.size());
0039     for (auto* target : targets) {
0040         ret += target->text();
0041     }
0042 
0043     return ret;
0044 }
0045 
0046 /**
0047  * Returns the first non-empty list of targets in folder @p item
0048  * or any of its ancestors if possible
0049  */
0050 static QStringList closestTargetsForFolder(KDevelop::ProjectFolderItem* item)
0051 {
0052     KDevelop::ProjectFolderItem* current = item;
0053     while (current) {
0054         const QStringList targets = targetsInFolder(current);
0055         if (!targets.isEmpty()) {
0056             return targets;
0057         }
0058         current = (current->parent() ? current->parent()->folder() : nullptr);
0059     }
0060     return QStringList();
0061 }
0062 
0063 static QStringList argumentsForItem(KDevelop::ProjectBaseItem* item)
0064 {
0065     if (!item->parent() &&
0066         QFile::exists(item->project()->buildSystemManager()->buildDirectory(item->project()->projectItem()).toLocalFile())) {
0067         return QStringList();
0068     }
0069 
0070     switch (item->type()) {
0071     case KDevelop::ProjectBaseItem::File:
0072         return QStringList(item->path().toLocalFile() + QLatin1Char('^'));
0073     case KDevelop::ProjectBaseItem::Target:
0074     case KDevelop::ProjectBaseItem::ExecutableTarget:
0075     case KDevelop::ProjectBaseItem::LibraryTarget:
0076         return QStringList(item->target()->text());
0077     case KDevelop::ProjectBaseItem::Folder:
0078     case KDevelop::ProjectBaseItem::BuildFolder:
0079         return closestTargetsForFolder(item->folder());
0080     }
0081     return QStringList();
0082 }
0083 
0084 NinjaJob* NinjaBuilder::runNinja(KDevelop::ProjectBaseItem* item, NinjaJob::CommandType commandType,
0085                                            const QStringList& args, const QByteArray& signal)
0086 {
0087     ///Running the same builder twice may result in serious problems,
0088     ///so kill jobs already running on the same project
0089     const auto ninjaJobs = m_activeNinjaJobs.data();
0090     for (NinjaJob* ninjaJob : ninjaJobs) {
0091         if (item && ninjaJob->item() && ninjaJob->item()->project() == item->project() && ninjaJob->commandType() == commandType) {
0092             qCDebug(NINJABUILDER) << "killing running ninja job, due to new started build on same project:" << ninjaJob;
0093             ninjaJob->kill();
0094         }
0095     }
0096 
0097     // Build arguments using data from KCM
0098     QStringList jobArguments;
0099     KSharedConfigPtr config = item->project()->projectConfiguration();
0100     KConfigGroup group = config->group("NinjaBuilder");
0101 
0102     if (!group.readEntry("Abort on First Error", true)) {
0103         jobArguments << QStringLiteral("-k");
0104     }
0105     if (group.readEntry("Override Number Of Jobs", false)) {
0106         int jobCount = group.readEntry("Number Of Jobs", 1);
0107         if (jobCount > 0) {
0108             jobArguments << QStringLiteral("-j%1").arg(jobCount);
0109         }
0110     }
0111     int errorCount = group.readEntry("Number Of Errors", 1);
0112     if (errorCount > 1) {
0113         jobArguments << QStringLiteral("-k%1").arg(errorCount);
0114     }
0115     if (group.readEntry("Display Only", false)) {
0116         jobArguments << QStringLiteral("-n");
0117     }
0118     QString extraOptions = group.readEntry("Additional Options", QString());
0119     if (!extraOptions.isEmpty()) {
0120         const auto options = KShell::splitArgs(extraOptions);
0121         for (const QString& option : options) {
0122             jobArguments << option;
0123         }
0124     }
0125     jobArguments << args;
0126 
0127     auto* job = new NinjaJob(item, commandType, jobArguments, signal, this);
0128     job->setEnvironmentProfile(group.readEntry("Default Ninja Environment Profile", QString()));
0129 
0130     m_activeNinjaJobs.append(job);
0131     return job;
0132 }
0133 
0134 KJob* NinjaBuilder::build(KDevelop::ProjectBaseItem* item)
0135 {
0136     return runNinja(item, NinjaJob::BuildCommand, argumentsForItem(item), "built");
0137 }
0138 
0139 KJob* NinjaBuilder::clean(KDevelop::ProjectBaseItem* item)
0140 {
0141     return runNinja(item, NinjaJob::CleanCommand, QStringList(QStringLiteral("-t")) << QStringLiteral("clean"), "cleaned");
0142 }
0143 
0144 KJob* NinjaBuilder::install(KDevelop::ProjectBaseItem* item)
0145 {
0146     NinjaJob* installJob = runNinja(item, NinjaJob::InstallCommand, QStringList(QStringLiteral("install")), "installed");
0147     installJob->setIsInstalling(true);
0148 
0149     KSharedConfigPtr configPtr = item->project()->projectConfiguration();
0150     KConfigGroup builderGroup(configPtr, "NinjaBuilder");
0151     bool installAsRoot = builderGroup.readEntry("Install As Root", false);
0152     if (installAsRoot) {
0153         auto* job = new KDevelop::BuilderJob;
0154         job->addCustomJob(KDevelop::BuilderJob::Build, build(item), item);
0155         job->addCustomJob(KDevelop::BuilderJob::Install, installJob, item);
0156         job->updateJobName();
0157         return job;
0158     } else {
0159         return installJob;
0160     }
0161 }
0162 
0163 int NinjaBuilder::perProjectConfigPages() const
0164 {
0165     return 1;
0166 }
0167 
0168 KDevelop::ConfigPage* NinjaBuilder::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent)
0169 {
0170     if (number == 0) {
0171         return new NinjaBuilderPreferences(this, options, parent);
0172     }
0173     return nullptr;
0174 }
0175 
0176 class ErrorJob
0177     : public KJob
0178 {
0179     Q_OBJECT
0180 public:
0181     ErrorJob(QObject* parent, const QString& error)
0182         : KJob(parent)
0183         , m_error(error)
0184     {}
0185 
0186     void start() override
0187     {
0188         setError(!m_error.isEmpty());
0189         setErrorText(m_error);
0190         emitResult();
0191     }
0192 
0193 private:
0194     QString m_error;
0195 };
0196 
0197 KJob* NinjaBuilder::install(KDevelop::ProjectBaseItem* dom, const QUrl& installPath)
0198 {
0199     return installPath.isEmpty() ? install(dom) : new ErrorJob(nullptr, i18n("Cannot specify prefix in %1, on ninja", installPath.toDisplayString()));
0200 }
0201 
0202 #include "ninjabuilder.moc"
0203 #include "moc_ninjabuilder.cpp"