File indexing completed on 2024-05-12 08:39:56
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 "ninjajob.h" 0009 0010 #include "ninjabuilder.h" 0011 0012 #include <outputview/outputfilteringstrategies.h> 0013 #include <project/interfaces/ibuildsystemmanager.h> 0014 #include <project/projectmodel.h> 0015 #include <interfaces/iproject.h> 0016 #include <interfaces/icore.h> 0017 #include <interfaces/iprojectcontroller.h> 0018 0019 #include <KLocalizedString> 0020 #include <KConfigGroup> 0021 0022 #include <QFile> 0023 #include <QRegularExpression> 0024 #include <QStandardPaths> 0025 #include <QUrl> 0026 0027 using namespace KDevelop; 0028 0029 class NinjaJobCompilerFilterStrategy 0030 : public CompilerFilterStrategy 0031 { 0032 public: 0033 using CompilerFilterStrategy::CompilerFilterStrategy; 0034 0035 IFilterStrategy::Progress progressInLine(const QString& line) override; 0036 }; 0037 0038 IFilterStrategy::Progress NinjaJobCompilerFilterStrategy::progressInLine(const QString& line) 0039 { 0040 // example string: [87/88] Building CXX object projectbuilders/ninjabuilder/CMakeFiles/kdevninja.dir/ninjajob.cpp.o 0041 static const QRegularExpression re(QStringLiteral("^\\[([0-9]+)\\/([0-9]+)\\] (.*)")); 0042 0043 QRegularExpressionMatch match = re.match(line); 0044 if (match.hasMatch()) { 0045 const int current = match.capturedRef(1).toInt(); 0046 const int total = match.capturedRef(2).toInt(); 0047 if (current && total) { 0048 // this is output from ninja 0049 const QString action = match.captured(3); 0050 const int percent = qRound(( float )current / total * 100); 0051 return { 0052 action, percent 0053 }; 0054 } 0055 } 0056 0057 return {}; 0058 } 0059 0060 NinjaJob::NinjaJob(KDevelop::ProjectBaseItem* item, CommandType commandType, 0061 const QStringList& arguments, const QByteArray& signal, NinjaBuilder* parent) 0062 : OutputExecuteJob(parent) 0063 , m_isInstalling(false) 0064 , m_idx(item->index()) 0065 , m_commandType(commandType) 0066 , m_signal(signal) 0067 , m_plugin(parent) 0068 { 0069 auto bsm = item->project()->buildSystemManager(); 0070 auto buildDir = bsm->buildDirectory(item); 0071 0072 setToolTitle(i18n("Ninja")); 0073 setCapabilities(Killable); 0074 setStandardToolView(KDevelop::IOutputView::BuildView); 0075 setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); 0076 setFilteringStrategy(new NinjaJobCompilerFilterStrategy(buildDir.toUrl())); 0077 setProperties(NeedWorkingDirectory | PortableMessages | DisplayStderr | IsBuilderHint | PostProcessOutput); 0078 0079 // hardcode the ninja output format so we can parse it reliably 0080 addEnvironmentOverride(QStringLiteral("NINJA_STATUS"), QStringLiteral("[%s/%t] ")); 0081 0082 *this << ninjaExecutable(); 0083 *this << arguments; 0084 0085 QStringList targets; 0086 for (const QString& arg : arguments) { 0087 if (!arg.startsWith(QLatin1Char('-'))) { 0088 targets << arg; 0089 } 0090 } 0091 0092 QString title; 0093 if (!targets.isEmpty()) { 0094 title = i18n("Ninja (%1): %2", item->text(), targets.join(QLatin1Char(' '))); 0095 } else { 0096 title = i18n("Ninja (%1)", item->text()); 0097 } 0098 setJobName(title); 0099 0100 connect(this, &NinjaJob::finished, this, &NinjaJob::emitProjectBuilderSignal); 0101 } 0102 0103 NinjaJob::~NinjaJob() 0104 { 0105 // prevent crash when emitting KJob::finished from ~KJob 0106 // (=> at this point NinjaJob is already destructed...) 0107 disconnect(this, &NinjaJob::finished, this, &NinjaJob::emitProjectBuilderSignal); 0108 } 0109 0110 void NinjaJob::setIsInstalling(bool isInstalling) 0111 { 0112 m_isInstalling = isInstalling; 0113 } 0114 0115 QString NinjaJob::ninjaExecutable() 0116 { 0117 QString path = QStandardPaths::findExecutable(QStringLiteral("ninja-build")); 0118 if (path.isEmpty()) { 0119 path = QStandardPaths::findExecutable(QStringLiteral("ninja")); 0120 } 0121 return path; 0122 } 0123 0124 QUrl NinjaJob::workingDirectory() const 0125 { 0126 KDevelop::ProjectBaseItem* it = item(); 0127 if (!it) { 0128 return QUrl(); 0129 } 0130 KDevelop::IBuildSystemManager* bsm = it->project()->buildSystemManager(); 0131 KDevelop::Path workingDir = bsm->buildDirectory(it); 0132 while (!QFile::exists(workingDir.toLocalFile() + QLatin1String("build.ninja"))) { 0133 KDevelop::Path upWorkingDir = workingDir.parent(); 0134 if (!upWorkingDir.isValid() || upWorkingDir == workingDir) { 0135 return bsm->buildDirectory(it->project()->projectItem()).toUrl(); 0136 } 0137 workingDir = upWorkingDir; 0138 } 0139 return workingDir.toUrl(); 0140 } 0141 0142 QStringList NinjaJob::privilegedExecutionCommand() const 0143 { 0144 KDevelop::ProjectBaseItem* it = item(); 0145 if (!it) { 0146 return QStringList(); 0147 } 0148 KSharedConfigPtr configPtr = it->project()->projectConfiguration(); 0149 KConfigGroup builderGroup(configPtr, "NinjaBuilder"); 0150 0151 bool runAsRoot = builderGroup.readEntry("Install As Root", false); 0152 if (runAsRoot && m_isInstalling) { 0153 int suCommand = builderGroup.readEntry("Su Command", 0); 0154 QStringList arguments; 0155 switch (suCommand) { 0156 case 1: 0157 return QStringList{QStringLiteral("kdesudo"), QStringLiteral("-t")}; 0158 0159 case 2: 0160 return QStringList{QStringLiteral("sudo")}; 0161 0162 default: 0163 return QStringList{QStringLiteral("kdesu"), QStringLiteral("-t")}; 0164 } 0165 } 0166 return QStringList(); 0167 } 0168 0169 void NinjaJob::emitProjectBuilderSignal(KJob* job) 0170 { 0171 if (!m_plugin) { 0172 return; 0173 } 0174 0175 KDevelop::ProjectBaseItem* it = item(); 0176 if (!it) { 0177 return; 0178 } 0179 0180 if (job->error() == 0) { 0181 Q_ASSERT(!m_signal.isEmpty()); 0182 QMetaObject::invokeMethod(m_plugin, m_signal.constData(), Q_ARG(KDevelop::ProjectBaseItem*, it)); 0183 } else { 0184 QMetaObject::invokeMethod(m_plugin, "failed", Q_ARG(KDevelop::ProjectBaseItem*, it)); 0185 } 0186 } 0187 0188 void NinjaJob::postProcessStderr(const QStringList& lines) 0189 { 0190 appendLines(lines); 0191 } 0192 0193 void NinjaJob::postProcessStdout(const QStringList& lines) 0194 { 0195 appendLines(lines); 0196 } 0197 0198 void NinjaJob::appendLines(const QStringList& lines) 0199 { 0200 if (lines.isEmpty()) { 0201 return; 0202 } 0203 0204 QStringList ret(lines); 0205 bool prev = false; 0206 for (QStringList::iterator it = ret.end(); it != ret.begin(); ) { 0207 --it; 0208 bool curr = it->startsWith(QLatin1Char('[')); 0209 if ((prev && curr) || it->endsWith(QLatin1String("] "))) { 0210 it = ret.erase(it); 0211 } 0212 prev = curr; 0213 } 0214 0215 model()->appendLines(ret); 0216 } 0217 0218 KDevelop::ProjectBaseItem* NinjaJob::item() const 0219 { 0220 return KDevelop::ICore::self()->projectController()->projectModel()->itemFromIndex(m_idx); 0221 } 0222 0223 NinjaJob::CommandType NinjaJob::commandType() const 0224 { 0225 return m_commandType; 0226 } 0227 0228 #include "moc_ninjajob.cpp"