File indexing completed on 2024-05-05 04:40:22
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 }