File indexing completed on 2024-04-28 04:38:58
0001 /* 0002 SPDX-FileCopyrightText: 2004 Roberto Raggi <roberto@kdevelop.org> 0003 SPDX-FileCopyrightText: 2007 Andreas Pakulat <apaku@gmx.de> 0004 SPDX-FileCopyrightText: 2007 Dukju Ahn <dukjuahn@gmail.com> 0005 SPDX-FileCopyrightText: 2008 Hamish Rodda <rodda@kde.org> 0006 SPDX-FileCopyrightText: 2012 Ivan Shapovalov <intelfx100@gmail.com> 0007 0008 SPDX-License-Identifier: LGPL-2.0-or-later 0009 */ 0010 0011 #include "makejob.h" 0012 0013 #include <QFileInfo> 0014 #include <QRegularExpression> 0015 #include <QThread> 0016 0017 #include <KShell> 0018 #include <KConfigGroup> 0019 #include <KLocalizedString> 0020 0021 #include <interfaces/iproject.h> 0022 #include <interfaces/icore.h> 0023 #include <interfaces/iprojectcontroller.h> 0024 #include <project/projectmodel.h> 0025 #include <project/interfaces/ibuildsystemmanager.h> 0026 #include <outputview/outputfilteringstrategies.h> 0027 0028 #include "makebuilder.h" 0029 #include "makebuilderpreferences.h" 0030 #include "debug.h" 0031 0032 using namespace KDevelop; 0033 0034 class MakeJobCompilerFilterStrategy : public CompilerFilterStrategy 0035 { 0036 public: 0037 using CompilerFilterStrategy::CompilerFilterStrategy; 0038 0039 IFilterStrategy::Progress progressInLine(const QString& line) override; 0040 }; 0041 0042 IFilterStrategy::Progress MakeJobCompilerFilterStrategy::progressInLine(const QString& line) 0043 { 0044 // example string: [ 97%] Built target clang-parser 0045 static const QRegularExpression re(QStringLiteral("^\\[([\\d ][\\d ]\\d)%\\] (.*)")); 0046 0047 QRegularExpressionMatch match = re.match(line); 0048 if (match.hasMatch()) { 0049 bool ok; 0050 const int percent = match.capturedRef(1).toInt(&ok); 0051 if (ok) { 0052 // this is output from make, likely 0053 const QString action = match.captured(2); 0054 return {action, percent}; 0055 } 0056 } 0057 0058 return {}; 0059 } 0060 0061 MakeJob::MakeJob(QObject* parent, KDevelop::ProjectBaseItem* item, 0062 CommandType c, const QStringList& overrideTargets, 0063 const MakeVariables& variables ) 0064 : OutputExecuteJob(parent) 0065 , m_idx(item->index()) 0066 , m_command(c) 0067 , m_overrideTargets(overrideTargets) 0068 , m_variables(variables) 0069 { 0070 auto bsm = item->project()->buildSystemManager(); 0071 auto buildDir = bsm->buildDirectory(item); 0072 0073 Q_ASSERT(item && item->model() && m_idx.isValid() && this->item() == item); 0074 setCapabilities( Killable ); 0075 setFilteringStrategy(new MakeJobCompilerFilterStrategy(buildDir.toUrl())); 0076 setProperties( NeedWorkingDirectory | PortableMessages | DisplayStderr | IsBuilderHint ); 0077 0078 QString title; 0079 if( !m_overrideTargets.isEmpty() ) 0080 title = i18n("Make (%1): %2", item->text(), m_overrideTargets.join(QLatin1Char(' '))); 0081 else 0082 title = i18n("Make (%1)", item->text()); 0083 setJobName( title ); 0084 setToolTitle( i18n("Make") ); 0085 } 0086 0087 MakeJob::~MakeJob() 0088 { 0089 } 0090 0091 void MakeJob::start() 0092 { 0093 ProjectBaseItem* it = item(); 0094 qCDebug(KDEV_MAKEBUILDER) << "Building with make" << m_command << m_overrideTargets.join(QLatin1Char(' ')); 0095 if (!it) 0096 { 0097 setError(ItemNoLongerValidError); 0098 setErrorText(i18n("Build item no longer available")); 0099 emitResult(); 0100 return; 0101 } 0102 0103 if( it->type() == KDevelop::ProjectBaseItem::File ) { 0104 setError(IncorrectItemError); 0105 setErrorText(i18n("Internal error: cannot build a file item")); 0106 emitResult(); 0107 return; 0108 } 0109 0110 setStandardToolView(IOutputView::BuildView); 0111 setBehaviours(KDevelop::IOutputView::AllowUserClose | KDevelop::IOutputView::AutoScroll); 0112 0113 OutputExecuteJob::start(); 0114 } 0115 0116 KDevelop::ProjectBaseItem * MakeJob::item() const 0117 { 0118 return ICore::self()->projectController()->projectModel()->itemFromIndex(m_idx); 0119 } 0120 0121 MakeJob::CommandType MakeJob::commandType() const 0122 { 0123 return m_command; 0124 } 0125 0126 QStringList MakeJob::customTargets() const 0127 { 0128 return m_overrideTargets; 0129 } 0130 0131 QUrl MakeJob::workingDirectory() const 0132 { 0133 ProjectBaseItem* it = item(); 0134 if(!it) 0135 return QUrl(); 0136 0137 KDevelop::IBuildSystemManager *bldMan = it->project()->buildSystemManager(); 0138 if( bldMan ) 0139 return bldMan->buildDirectory( it ).toUrl(); // the correct build dir 0140 else 0141 { 0142 // Just build in-source, where the build directory equals the one with particular target/source. 0143 for( ProjectBaseItem* item = it; item; item = item->parent() ) { 0144 switch( item->type() ) { 0145 case KDevelop::ProjectBaseItem::Folder: 0146 case KDevelop::ProjectBaseItem::BuildFolder: 0147 return static_cast<KDevelop::ProjectFolderItem*>(item)->path().toUrl(); 0148 case KDevelop::ProjectBaseItem::Target: 0149 case KDevelop::ProjectBaseItem::File: 0150 break; 0151 } 0152 } 0153 return QUrl(); 0154 } 0155 } 0156 0157 QStringList MakeJob::privilegedExecutionCommand() const 0158 { 0159 ProjectBaseItem* it = item(); 0160 if(!it) 0161 return QStringList(); 0162 KSharedConfigPtr configPtr = it->project()->projectConfiguration(); 0163 KConfigGroup builderGroup( configPtr, "MakeBuilder" ); 0164 0165 bool runAsRoot = builderGroup.readEntry( "Install As Root", false ); 0166 if ( runAsRoot && m_command == InstallCommand ) 0167 { 0168 QString suCommand = builderGroup.readEntry( "Su Command", QString() ); 0169 bool suCommandIsDigit; 0170 QStringList suCommandWithArg; 0171 int suCommandNum = suCommand.toInt(&suCommandIsDigit); 0172 0173 /* 0174 * "if(suCommandIsDigit)" block exists only because of backwards compatibility 0175 * reasons, In earlier versions of KDevelop, suCommand's type was 0176 * int, if a user upgrades to current version from an older version, 0177 * suCommandIsDigit will become "true" and we will set suCommand according 0178 * to the stored config entry. 0179 */ 0180 if(suCommandIsDigit) 0181 { 0182 switch(suCommandNum) 0183 { 0184 case 1: 0185 { 0186 suCommand = QStringLiteral("kdesudo"); 0187 break; 0188 } 0189 case 2: 0190 { 0191 suCommand = QStringLiteral("sudo"); 0192 break; 0193 } 0194 default: 0195 suCommand = QStringLiteral("kdesu"); 0196 } 0197 0198 builderGroup.writeEntry("Su Command", suCommand); 0199 //so that suCommandIsDigit becomes false next time 0200 //project is opened. 0201 } 0202 0203 suCommandWithArg = KShell::splitArgs(suCommand); 0204 if( suCommandWithArg.isEmpty() ) 0205 { 0206 suCommandWithArg = QStringList{QStringLiteral("kdesu"), QStringLiteral("-t")}; 0207 } 0208 0209 return suCommandWithArg; 0210 } 0211 return QStringList(); 0212 } 0213 0214 QStringList MakeJob::commandLine() const 0215 { 0216 ProjectBaseItem* it = item(); 0217 if(!it) 0218 return QStringList(); 0219 QStringList cmdline; 0220 0221 KSharedConfigPtr configPtr = it->project()->projectConfiguration(); 0222 KConfigGroup builderGroup( configPtr, "MakeBuilder" ); 0223 0224 // TODO: migrate to more generic key term "Make Executable" 0225 QString makeBin = builderGroup.readEntry("Make Binary", MakeBuilderPreferences::standardMakeExecutable()); 0226 cmdline << makeBin; 0227 0228 if( ! builderGroup.readEntry("Abort on First Error", true)) 0229 { 0230 cmdline << (isNMake(makeBin) ? QStringLiteral("/K") : QStringLiteral("-k")); 0231 } 0232 0233 // note: nmake does not support the -j flag 0234 if (!isNMake(makeBin)) { 0235 if (builderGroup.readEntry("Override Number Of Jobs", false)) { 0236 int jobCount = builderGroup.readEntry("Number Of Jobs", 1); 0237 if (jobCount > 0) { 0238 cmdline << QStringLiteral("-j%1").arg(jobCount); 0239 } 0240 } else { 0241 // use the ideal thread count by default 0242 cmdline << QStringLiteral("-j%1").arg(QThread::idealThreadCount()); 0243 } 0244 } 0245 0246 if( builderGroup.readEntry("Display Only", false) ) 0247 { 0248 cmdline << (isNMake(makeBin) ? QStringLiteral("/N") : QStringLiteral("-n")); 0249 } 0250 0251 QString extraOptions = builderGroup.readEntry("Additional Options", QString()); 0252 if( ! extraOptions.isEmpty() ) 0253 { 0254 const auto options = KShell::splitArgs(extraOptions); 0255 for (const QString& option : options) { 0256 cmdline << option; 0257 } 0258 } 0259 0260 for (auto& variable : m_variables) { 0261 cmdline += variable.first + QLatin1Char('=') + variable.second; 0262 } 0263 0264 if( m_overrideTargets.isEmpty() ) 0265 { 0266 QString target; 0267 switch (it->type()) { 0268 case KDevelop::ProjectBaseItem::Target: 0269 case KDevelop::ProjectBaseItem::ExecutableTarget: 0270 case KDevelop::ProjectBaseItem::LibraryTarget: 0271 Q_ASSERT(it->target()); 0272 cmdline << it->target()->text(); 0273 break; 0274 case KDevelop::ProjectBaseItem::BuildFolder: 0275 target = builderGroup.readEntry("Default Target", QString()); 0276 if( !target.isEmpty() ) 0277 cmdline << target; 0278 break; 0279 default: break; 0280 } 0281 }else 0282 { 0283 cmdline += m_overrideTargets; 0284 } 0285 0286 return cmdline; 0287 } 0288 0289 QString MakeJob::environmentProfile() const 0290 { 0291 ProjectBaseItem* it = item(); 0292 if(!it) 0293 return QString(); 0294 KSharedConfigPtr configPtr = it->project()->projectConfiguration(); 0295 KConfigGroup builderGroup( configPtr, "MakeBuilder" ); 0296 return builderGroup.readEntry( "Default Make Environment Profile", QString() ); 0297 } 0298 0299 bool MakeJob::isNMake(const QString& makeBin) 0300 { 0301 return !QFileInfo(makeBin).baseName().compare(QLatin1String("nmake"), Qt::CaseInsensitive); 0302 } 0303 0304 #include "moc_makejob.cpp"