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

0001 /*
0002     SPDX-FileCopyrightText: 2006 Andreas Pakulat <apaku@gmx.de>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "qmakeprojectfile.h"
0008 
0009 #include <QList>
0010 #include <QStringList>
0011 #include <QDir>
0012 
0013 #include "debug.h"
0014 #include "parser/ast.h"
0015 #include "qmakecache.h"
0016 #include "qmakemkspecs.h"
0017 #include "qmakeconfig.h"
0018 
0019 #include <interfaces/iproject.h>
0020 #include <util/path.h>
0021 
0022 #define ifDebug(x)
0023 
0024 QHash<QString, QHash<QString, QString>> QMakeProjectFile::m_qmakeQueryCache;
0025 
0026 const QStringList QMakeProjectFile::FileVariables = QStringList{
0027     QStringLiteral("IDLS"),
0028     QStringLiteral("RESOURCES"),
0029     QStringLiteral("IMAGES"),
0030     QStringLiteral("LEXSOURCES"),
0031     QStringLiteral("DISTFILES"),
0032     QStringLiteral("YACCSOURCES"),
0033     QStringLiteral("TRANSLATIONS"),
0034     QStringLiteral("HEADERS"),
0035     QStringLiteral("SOURCES"),
0036     QStringLiteral("INTERFACES"),
0037     QStringLiteral("FORMS"),
0038 };
0039 
0040 QMakeProjectFile::QMakeProjectFile(const QString& projectfile)
0041     : QMakeFile(projectfile)
0042     , m_mkspecs(nullptr)
0043     , m_cache(nullptr)
0044 {
0045 }
0046 
0047 void QMakeProjectFile::setQMakeCache(QMakeCache* cache)
0048 {
0049     m_cache = cache;
0050 }
0051 
0052 void QMakeProjectFile::setMkSpecs(QMakeMkSpecs* mkspecs)
0053 {
0054     m_mkspecs = mkspecs;
0055 }
0056 
0057 void QMakeProjectFile::setOwnMkSpecs(bool own)
0058 {
0059     m_ownMkSpecs = own;
0060 }
0061 
0062 bool QMakeProjectFile::read()
0063 {
0064     // default values
0065     // NOTE: if we already have such a var, e.g. in an include file, we must not overwrite it here!
0066     if (!m_variableValues.contains(QStringLiteral("QT"))) {
0067         m_variableValues[QStringLiteral("QT")] = QStringList{QStringLiteral("core"), QStringLiteral("gui")};
0068     }
0069     if (!m_variableValues.contains(QStringLiteral("CONFIG"))) {
0070         m_variableValues[QStringLiteral("CONFIG")] = QStringList() << QStringLiteral("qt");
0071     }
0072 
0073     Q_ASSERT(m_mkspecs);
0074     auto addVars = [&](const auto& vars) {
0075         for (auto it = vars.begin(), end = vars.end(); it != end; ++it) {
0076             if (!m_variableValues.contains(it.key())) {
0077                 m_variableValues.insert(it.key(), it.value());
0078             }
0079         }
0080     };
0081     addVars(m_mkspecs->variableMap());
0082     if (m_cache) {
0083         addVars(m_cache->variableMap());
0084     }
0085 
0086     /// TODO: more special variables
0087     m_variableValues[QStringLiteral("PWD")] = QStringList() << pwd();
0088     m_variableValues[QStringLiteral("_PRO_FILE_")] = QStringList() << proFile();
0089     m_variableValues[QStringLiteral("_PRO_FILE_PWD_")] = QStringList() << proFilePwd();
0090     m_variableValues[QStringLiteral("OUT_PWD")] = QStringList() << outPwd();
0091 
0092     const QString qtInstallHeaders = QStringLiteral("QT_INSTALL_HEADERS");
0093     const QString qtVersion = QStringLiteral("QT_VERSION");
0094     const QString qtInstallLibs = QStringLiteral("QT_INSTALL_LIBS");
0095 
0096     const QString executable = QMakeConfig::qmakeExecutable(project());
0097     if (!m_qmakeQueryCache.contains(executable)) {
0098         const auto queryResult = QMakeConfig::queryQMake(executable, {qtInstallHeaders, qtVersion, qtInstallLibs});
0099         if (queryResult.isEmpty()) {
0100             qCWarning(KDEV_QMAKE) << "Failed to query qmake - bad qmake executable configured?" << executable;
0101         }
0102         m_qmakeQueryCache[executable] = queryResult;
0103     }
0104 
0105     const auto cachedQueryResult = m_qmakeQueryCache.value(executable);
0106     m_qtIncludeDir = cachedQueryResult.value(qtInstallHeaders);
0107     m_qtVersion = cachedQueryResult.value(qtVersion);
0108     m_qtLibDir = cachedQueryResult.value(qtInstallLibs);
0109 
0110     return QMakeFile::read();
0111 }
0112 
0113 QStringList QMakeProjectFile::subProjects() const
0114 {
0115     ifDebug(qCDebug(KDEV_QMAKE) << "Fetching subprojects";) QStringList list;
0116     const auto& subdirs = variableValues(QStringLiteral("SUBDIRS"));
0117     for (QString subdir : subdirs) {
0118         QString fileOrPath;
0119         ifDebug(qCDebug(KDEV_QMAKE) << "Found value:" << subdir;) if (containsVariable(subdir + QLatin1String(".file"))
0120                                                                       && !variableValues(subdir + QLatin1String(".file")).isEmpty())
0121         {
0122             subdir = variableValues(subdir + QLatin1String(".file")).first();
0123         }
0124         else if (containsVariable(subdir + QLatin1String(".subdir")) && !variableValues(subdir + QLatin1String(".subdir")).isEmpty())
0125         {
0126             subdir = variableValues(subdir + QLatin1String(".subdir")).first();
0127         }
0128         if (subdir.endsWith(QLatin1String(".pro"))) {
0129             fileOrPath = resolveToSingleFileName(subdir.trimmed());
0130         } else {
0131             fileOrPath = resolveToSingleFileName(subdir.trimmed());
0132         }
0133         if (fileOrPath.isEmpty()) {
0134             qCWarning(KDEV_QMAKE) << "could not resolve subdir" << subdir << "to file or path, skipping";
0135             continue;
0136         }
0137         list << fileOrPath;
0138     }
0139 
0140     ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "subprojects";) return list;
0141 }
0142 
0143 bool QMakeProjectFile::hasSubProject(const QString& file) const
0144 {
0145     const auto subs = subProjects();
0146     for (const auto& sub : subs) {
0147         if (sub == file) {
0148             return true;
0149         } else if (QFileInfo(file).absoluteDir() == sub) {
0150             return true;
0151         }
0152     }
0153     return false;
0154 }
0155 
0156 void QMakeProjectFile::addPathsForVariable(const QString& variable, QStringList* list, const QString& base) const
0157 {
0158     const QStringList values = variableValues(variable);
0159     ifDebug(qCDebug(KDEV_QMAKE) << variable << values;) for (const QString& val : values)
0160     {
0161         QString path = resolveToSingleFileName(val, base);
0162         if (!path.isEmpty() && !list->contains(val)) {
0163             list->append(path);
0164         }
0165     }
0166 }
0167 
0168 QStringList QMakeProjectFile::includeDirectories() const
0169 {
0170     ifDebug(qCDebug(KDEV_QMAKE) << "Fetching include dirs" << m_qtIncludeDir;)
0171         ifDebug(qCDebug(KDEV_QMAKE) << "CONFIG" << variableValues("CONFIG");)
0172 
0173             QStringList list;
0174     addPathsForVariable(QStringLiteral("INCLUDEPATH"), &list);
0175     addPathsForVariable(QStringLiteral("QMAKE_INCDIR"), &list);
0176     if (variableValues(QStringLiteral("CONFIG")).contains(QStringLiteral("opengl"))) {
0177         addPathsForVariable(QStringLiteral("QMAKE_INCDIR_OPENGL"), &list);
0178     }
0179     if (variableValues(QStringLiteral("CONFIG")).contains(QStringLiteral("qt"))) {
0180         if (!list.contains(m_qtIncludeDir))
0181             list << m_qtIncludeDir;
0182 
0183         QDir incDir(m_qtIncludeDir);
0184         auto modules = variableValues(QStringLiteral("QT"));
0185         if (!modules.isEmpty() && !modules.contains(QStringLiteral("core"))) {
0186             // TODO: proper dependency tracking of modules
0187             // for now, at least include core if we include any other module
0188             modules << QStringLiteral("core");
0189         }
0190 
0191         // TODO: This is all very fragile, should rather read QMake module .pri files (e.g. qt_lib_core_private.pri)
0192         for (const auto& module : std::as_const(modules)) {
0193             QString pattern = module;
0194 
0195             bool isPrivate = false;
0196             const QLatin1String dashPrivateLineEnd("-private");
0197             const QLatin1String underscorePrivateLineEnd("_private");
0198             if (module.endsWith(dashPrivateLineEnd)) {
0199                 pattern.chop(dashPrivateLineEnd.size());
0200                 isPrivate = true;
0201             } else if (module.endsWith(underscorePrivateLineEnd)) {
0202                 // _private is less common, but still a valid suffix
0203                 pattern.chop(underscorePrivateLineEnd.size());
0204                 isPrivate = true;
0205             }
0206 
0207             if (pattern == QLatin1String("qtestlib") || pattern == QLatin1String("testlib")) {
0208                 pattern = QStringLiteral("QtTest");
0209             } else if (pattern == QLatin1String("qaxcontainer")) {
0210                 pattern = QStringLiteral("ActiveQt");
0211             } else if (pattern == QLatin1String("qaxserver")) {
0212                 pattern = QStringLiteral("ActiveQt");
0213             }
0214 
0215             QFileInfoList match = incDir.entryInfoList({QStringLiteral("Qt%1").arg(pattern)}, QDir::Dirs);
0216             if (match.isEmpty()) {
0217                 // try non-prefixed pattern
0218                 match = incDir.entryInfoList({pattern}, QDir::Dirs);
0219                 if (match.isEmpty()) {
0220                     qCWarning(KDEV_QMAKE) << "unhandled Qt module:" << module << pattern;
0221                     continue;
0222                 }
0223             }
0224 
0225             QString path = match.first().canonicalFilePath();
0226             if (isPrivate) {
0227                 path += QLatin1Char('/') + m_qtVersion + QLatin1Char('/') + match.first().fileName() + QLatin1String("/private/");
0228             }
0229             if (!list.contains(path)) {
0230                 list << path;
0231             }
0232         }
0233     }
0234 
0235     if (variableValues(QStringLiteral("CONFIG")).contains(QStringLiteral("thread"))) {
0236         addPathsForVariable(QStringLiteral("QMAKE_INCDIR_THREAD"), &list);
0237     }
0238     if (variableValues(QStringLiteral("CONFIG")).contains(QStringLiteral("x11"))) {
0239         addPathsForVariable(QStringLiteral("QMAKE_INCDIR_X11"), &list);
0240     }
0241 
0242     addPathsForVariable(QStringLiteral("MOC_DIR"), &list, outPwd());
0243     addPathsForVariable(QStringLiteral("OBJECTS_DIR"), &list, outPwd());
0244     addPathsForVariable(QStringLiteral("UI_DIR"), &list, outPwd());
0245 
0246     ifDebug(qCDebug(KDEV_QMAKE) << "final list:" << list;) return list;
0247 }
0248 
0249 // Scan QMAKE_C*FLAGS for -F and -iframework and QMAKE_LFLAGS for good measure. Time will
0250 // tell if we need to scan the release/debug/... specific versions of QMAKE_C*FLAGS.
0251 // Also include QT_INSTALL_LIBS which corresponds to Qt's framework directory on OS X.
0252 QStringList QMakeProjectFile::frameworkDirectories() const
0253 {
0254     const auto variablesToCheck = {QStringLiteral("QMAKE_CFLAGS"),
0255                                     QStringLiteral("QMAKE_CXXFLAGS"),
0256                                     QStringLiteral("QMAKE_LFLAGS")};
0257     const QLatin1String fOption("-F");
0258     const QLatin1String iframeworkOption("-iframework");
0259     QStringList fwDirs;
0260     for (const auto& var : variablesToCheck) {
0261         bool storeArg = false;
0262         const auto values = variableValues(var);
0263         for (const auto& arg : values) {
0264             if (arg == fOption || arg == iframeworkOption) {
0265                 // detached -F/-iframework arg; set a warrant to store the next argument
0266                 storeArg = true;
0267             } else {
0268                 if (arg.startsWith(fOption)) {
0269                     fwDirs << arg.mid(fOption.size());
0270                 } else if (arg.startsWith(iframeworkOption)) {
0271                     fwDirs << arg.mid(iframeworkOption.size());
0272                 } else if (storeArg) {
0273                     fwDirs << arg;
0274                 }
0275                 // cancel any outstanding warrants to store the next argument
0276                 storeArg = false;
0277             }
0278         }
0279     }
0280 #ifdef Q_OS_OSX
0281     fwDirs << m_qtLibDir;
0282 #endif
0283     return fwDirs;
0284 }
0285 
0286 QStringList QMakeProjectFile::extraArguments() const
0287 {
0288     const auto variablesToCheck = {QStringLiteral("QMAKE_CXXFLAGS")};
0289     const auto prefixes = { "-F", "-iframework", "-I", "-D" };
0290     QStringList args;
0291     for (const auto& var : variablesToCheck) {
0292         const auto values = variableValues(var);
0293         for (const auto& arg : values) {
0294             auto argHasPrefix = [&arg](const char* prefix) {
0295                 return arg.startsWith(QLatin1String(prefix));
0296             };
0297             if ( !std::any_of(prefixes.begin(), prefixes.end(), argHasPrefix)) {
0298                 args << arg;
0299             }
0300         }
0301     }
0302     return args;
0303 }
0304 
0305 QStringList QMakeProjectFile::files() const
0306 {
0307     ifDebug(qCDebug(KDEV_QMAKE) << "Fetching files";)
0308 
0309         QStringList list;
0310     for (const auto& variable : QMakeProjectFile::FileVariables) {
0311         const auto values = variableValues(variable);
0312         for (const auto& value : values) {
0313             list += resolveFileName(value);
0314         }
0315     }
0316     ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "files";) return list;
0317 }
0318 
0319 QStringList QMakeProjectFile::filesForTarget(const QString& s) const
0320 {
0321     ifDebug(qCDebug(KDEV_QMAKE) << "Fetching files";)
0322 
0323         QStringList list;
0324     if (variableValues(QStringLiteral("INSTALLS")).contains(s)) {
0325         const QStringList files = variableValues(s + QLatin1String(".files"));
0326         for (const QString& val : files) {
0327             list += QStringList(resolveFileName(val));
0328         }
0329     }
0330     if (!variableValues(QStringLiteral("INSTALLS")).contains(s) || s == QLatin1String("target")) {
0331         for (const QString& variable : QMakeProjectFile::FileVariables) {
0332             const auto values = variableValues(variable);
0333             for (const QString& value : values) {
0334                 list += QStringList(resolveFileName(value));
0335             }
0336         }
0337     }
0338     ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "files";) return list;
0339 }
0340 
0341 QString QMakeProjectFile::getTemplate() const
0342 {
0343     QString templ = QStringLiteral("app");
0344     if (!variableValues(QStringLiteral("TEMPLATE")).isEmpty()) {
0345         templ = variableValues(QStringLiteral("TEMPLATE")).first();
0346     }
0347     return templ;
0348 }
0349 
0350 QStringList QMakeProjectFile::targets() const
0351 {
0352     ifDebug(qCDebug(KDEV_QMAKE) << "Fetching targets";)
0353 
0354         QStringList list;
0355 
0356     list += variableValues(QStringLiteral("TARGET"));
0357     if (list.isEmpty() && getTemplate() != QLatin1String("subdirs")) {
0358         list += QFileInfo(absoluteFile()).baseName();
0359     }
0360 
0361     const auto& targets = variableValues(QStringLiteral("INSTALLS"));
0362     for (const QString& target : targets) {
0363         if (!target.isEmpty() && target != QLatin1String("target"))
0364             list << target;
0365     }
0366 
0367     if (list.removeAll(QString())) {
0368         // remove empty targets - which is probably a bug...
0369         qCWarning(KDEV_QMAKE) << "got empty entry in TARGET of file" << absoluteFile();
0370     }
0371 
0372     ifDebug(qCDebug(KDEV_QMAKE) << "found" << list.size() << "targets";) return list;
0373 }
0374 
0375 QMakeProjectFile::~QMakeProjectFile()
0376 {
0377     // TODO: delete cache, specs, ...?
0378     if (m_ownMkSpecs)
0379         delete m_mkspecs;
0380 }
0381 
0382 QStringList QMakeProjectFile::resolveVariable(const QString& variable, VariableInfo::VariableType type) const
0383 {
0384     if (type == VariableInfo::QtConfigVariable) {
0385         if (m_mkspecs->isQMakeInternalVariable(variable)) {
0386             return QStringList() << m_mkspecs->qmakeInternalVariable(variable);
0387         } else {
0388             qCWarning(KDEV_QMAKE) << "unknown QtConfig Variable:" << variable;
0389             return QStringList();
0390         }
0391     }
0392 
0393     return QMakeFile::resolveVariable(variable, type);
0394 }
0395 
0396 QMakeMkSpecs* QMakeProjectFile::mkSpecs() const
0397 {
0398     return m_mkspecs;
0399 }
0400 
0401 QMakeCache* QMakeProjectFile::qmakeCache() const
0402 {
0403     return m_cache;
0404 }
0405 
0406 QList<QMakeProjectFile::DefinePair> QMakeProjectFile::defines() const
0407 {
0408     QList<DefinePair> d;
0409     const auto& defs = variableMap().value(QStringLiteral("DEFINES"));
0410     for (const QString& def : defs) {
0411         int pos = def.indexOf(QLatin1Char('='));
0412         if (pos >= 0) {
0413             // a value is attached to define
0414             d.append(DefinePair(def.left(pos), def.mid(pos + 1)));
0415         } else {
0416             // a value-less define
0417             d.append(DefinePair(def, QString()));
0418         }
0419     }
0420     return d;
0421 }
0422 
0423 QString QMakeProjectFile::pwd() const
0424 {
0425     return absoluteDir();
0426 }
0427 
0428 QString QMakeProjectFile::outPwd() const
0429 {
0430     if (!project()) {
0431         return absoluteDir();
0432     } else {
0433         return QMakeConfig::buildDirFromSrc(project(), KDevelop::Path(absoluteDir())).toLocalFile();
0434     }
0435 }
0436 
0437 QString QMakeProjectFile::proFile() const
0438 {
0439     return absoluteFile();
0440 }
0441 
0442 QString QMakeProjectFile::proFilePwd() const
0443 {
0444     return absoluteDir();
0445 }