File indexing completed on 2024-05-05 16:46:16

0001 /*
0002     SPDX-License-Identifier: GPL-2.0-or-later
0003 */
0004 
0005 #include "qmakeconfig.h"
0006 
0007 #include <QDir>
0008 #include <QMutex>
0009 #include <QFileInfo>
0010 #include <QStandardPaths>
0011 
0012 #include <KConfigGroup>
0013 #include <KProcess>
0014 
0015 #include <interfaces/iproject.h>
0016 #include <util/path.h>
0017 #include <debug.h>
0018 
0019 const char QMakeConfig::CONFIG_GROUP[] = "QMake_Builder";
0020 
0021 // TODO: migrate to more generic & consistent key term "QMake_Executable"
0022 const char QMakeConfig::QMAKE_EXECUTABLE[] = "QMake_Binary";
0023 const char QMakeConfig::BUILD_FOLDER[] = "Build_Folder";
0024 const char QMakeConfig::INSTALL_PREFIX[] = "Install_Prefix";
0025 const char QMakeConfig::EXTRA_ARGUMENTS[] = "Extra_Arguments";
0026 const char QMakeConfig::BUILD_TYPE[] = "Build_Type";
0027 const char QMakeConfig::ALL_BUILDS[] = "All_Builds";
0028 
0029 using namespace KDevelop;
0030 
0031 /// NOTE: KConfig is not thread safe
0032 QMutex s_buildDirMutex;
0033 
0034 bool QMakeConfig::isConfigured(const IProject* project)
0035 {
0036     QMutexLocker lock(&s_buildDirMutex);
0037     KConfigGroup cg(project->projectConfiguration(), CONFIG_GROUP);
0038     return cg.exists() && cg.hasKey(QMAKE_EXECUTABLE) && cg.hasKey(BUILD_FOLDER);
0039 }
0040 
0041 Path QMakeConfig::buildDirFromSrc(const IProject* project, const Path& srcDir)
0042 {
0043     QMutexLocker lock(&s_buildDirMutex);
0044     KConfigGroup cg(project->projectConfiguration(), QMakeConfig::CONFIG_GROUP);
0045     Path buildDir = Path(cg.readEntry(QMakeConfig::BUILD_FOLDER, QString()));
0046     lock.unlock();
0047 
0048     if (buildDir.isValid()) {
0049         buildDir.addPath(project->path().relativePath(srcDir));
0050     }
0051     return buildDir;
0052 }
0053 
0054 QString QMakeConfig::qmakeExecutable(const IProject* project)
0055 {
0056     QMutexLocker lock(&s_buildDirMutex);
0057     QString exe;
0058     if (project) {
0059         KSharedConfig::Ptr cfg = project->projectConfiguration();
0060         KConfigGroup group(cfg.data(), CONFIG_GROUP);
0061         if (group.hasKey(QMAKE_EXECUTABLE)) {
0062             exe = group.readEntry(QMAKE_EXECUTABLE, QString());
0063             QFileInfo info(exe);
0064             if (!info.exists() || !info.isExecutable()) {
0065                 qCWarning(KDEV_QMAKE) << "bad QMake configured for project " << project->path().toUrl() << ":" << exe;
0066                 exe.clear();
0067             }
0068         }
0069     }
0070     if (exe.isEmpty()) {
0071         exe = QStandardPaths::findExecutable(QStringLiteral("qmake"));
0072     }
0073     if (exe.isEmpty()) {
0074         exe = QStandardPaths::findExecutable(QStringLiteral("qmake-qt5"));
0075     }
0076     if (exe.isEmpty()) {
0077         exe = QStandardPaths::findExecutable(QStringLiteral("qmake-qt4"));
0078     }
0079     Q_ASSERT(!exe.isEmpty());
0080     return exe;
0081 }
0082 
0083 QHash<QString, QString> QMakeConfig::queryQMake(const QString& qmakeExecutable, const QStringList& args)
0084 {
0085     QHash<QString, QString> hash;
0086     KProcess p;
0087     p.setOutputChannelMode(KProcess::OnlyStdoutChannel);
0088     p << qmakeExecutable << QStringLiteral("-query") << args;
0089 
0090     const int rc = p.execute();
0091     if (rc != 0) {
0092         qCWarning(KDEV_QMAKE) << "failed to execute qmake query " << p.program().join(QLatin1Char(' ')) << "return code was:" << rc;
0093         return QHash<QString, QString>();
0094     }
0095 
0096     // TODO: Qt 5.5: Use QTextStream::readLineInto
0097     QTextStream stream(&p);
0098     while (!stream.atEnd()) {
0099         const QString line = stream.readLine();
0100         const int colon = line.indexOf(QLatin1Char(':'));
0101         if (colon == -1) {
0102             continue;
0103         }
0104 
0105         const auto key = line.left(colon);
0106         const auto value = line.mid(colon + 1);
0107         hash.insert(key, value);
0108     }
0109     qCDebug(KDEV_QMAKE) << "Ran qmake (" << p.program().join(QLatin1Char(' ')) << "), found:" << hash;
0110     return hash;
0111 }
0112 
0113 QString QMakeConfig::findBasicMkSpec(const QHash<QString, QString>& qmakeVars)
0114 {
0115     QStringList paths;
0116     if (qmakeVars.contains(QStringLiteral("QMAKE_MKSPECS"))) {
0117         // qt4
0118         const auto mkspecDirs = qmakeVars[QStringLiteral("QMAKE_MKSPECS")].split(QDir::listSeparator());
0119         for (const auto& dir : mkspecDirs) {
0120             paths << dir + QLatin1String("/default/qmake.conf");
0121         }
0122     } else if (!qmakeVars.contains(QStringLiteral("QMAKE_MKSPECS")) && qmakeVars.contains(QStringLiteral("QMAKE_SPEC"))) {
0123         QString path;
0124         // qt5 doesn't have the MKSPECS nor default anymore
0125         // let's try to look up the mkspec path ourselves,
0126         // see QMakeEvaluator::updateMkspecPaths() in QMake source code as reference
0127         if (qmakeVars.contains(QStringLiteral("QT_HOST_DATA/src"))) {
0128             // >=qt5.2: since 0d463c05fc4f2e79e5a4e5a5382a1e6ed5d2615e (in Qt5 qtbase repository)
0129             // mkspecs are no longer copied to the build directory.
0130             // instead, we have to look them up in the source directory.
0131             // this commit also introduced the key 'QT_HOST_DATA/src' which we use here
0132             path = qmakeVars[QStringLiteral("QT_HOST_DATA/src")];
0133         } else if (qmakeVars.contains(QStringLiteral("QT_HOST_DATA"))) {
0134             // cross compilation
0135             path = qmakeVars[QStringLiteral("QT_HOST_DATA")];
0136         } else {
0137             Q_ASSERT(qmakeVars.contains(QStringLiteral("QT_INSTALL_PREFIX")));
0138             path = qmakeVars[QStringLiteral("QT_INSTALL_PREFIX")];
0139         }
0140         path += QLatin1String("/mkspecs/") + qmakeVars[QStringLiteral("QMAKE_SPEC")] + QLatin1String("/qmake.conf");
0141         paths << path;
0142     }
0143 
0144     for (const auto& path : std::as_const(paths)) {
0145         QFileInfo fi(path);
0146         if (fi.exists())
0147             return fi.absoluteFilePath();
0148     }
0149 
0150     return QString();
0151 }