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"