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"