File indexing completed on 2024-05-12 04:39:42
0001 /* 0002 SPDX-FileCopyrightText: 2007 David Nolden <david.nolden.kdevelop@art-master.de> 0003 SPDX-FileCopyrightText: 2014 Kevin Funk <kfunk@kde.org> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "makefileresolver.h" 0009 0010 #include "helper.h" 0011 0012 #include <memory> 0013 #include <cstdio> 0014 #include <iostream> 0015 0016 #include <QDir> 0017 #include <QFileInfo> 0018 #include <QMutex> 0019 #include <QMutexLocker> 0020 #include <QRegularExpression> 0021 #include <QRegExp> 0022 0023 #include <KProcess> 0024 #include <KLocalizedString> 0025 0026 #include <serialization/indexedstring.h> 0027 #include <util/pushvalue.h> 0028 #include <util/path.h> 0029 0030 // #define VERBOSE 0031 0032 #if defined(VERBOSE) 0033 #define ifTest(x) x 0034 #else 0035 #define ifTest(x) 0036 #endif 0037 0038 const int maximumInternalResolutionDepth = 3; 0039 0040 using namespace std; 0041 using namespace KDevelop; 0042 0043 namespace { 0044 ///After how many seconds should we retry? 0045 static const int CACHE_FAIL_FOR_SECONDS = 200; 0046 0047 static const int processTimeoutSeconds = 30; 0048 0049 struct CacheEntry 0050 { 0051 CacheEntry() 0052 { } 0053 ModificationRevisionSet modificationTime; 0054 Path::List paths; 0055 Path::List frameworkDirectories; 0056 QHash<QString, QString> defines; 0057 QString errorMessage, longErrorMessage; 0058 bool failed = false; 0059 QMap<QString,bool> failedFiles; 0060 QDateTime failTime; 0061 }; 0062 using Cache = QMap<QString, CacheEntry>; 0063 0064 static Cache s_cache; 0065 static QMutex s_cacheMutex; 0066 } 0067 0068 /** 0069 * Compatibility: 0070 * make/automake: Should work perfectly 0071 * cmake: Thanks to the path-recursion, this works with cmake(tested with version "2.4-patch 6" 0072 * with kdelibs out-of-source and with kdevelop4 in-source) 0073 **/ 0074 class SourcePathInformation 0075 { 0076 public: 0077 explicit SourcePathInformation(const QString& path) 0078 : m_path(path) 0079 { 0080 } 0081 0082 QString createCommand(const QString& absoluteFile, const QString& workingDirectory, const QString& makeParameters) const 0083 { 0084 QString relativeFile = Path(workingDirectory).relativePath(Path(absoluteFile)); 0085 #ifndef Q_OS_FREEBSD 0086 // GNU make implicitly enables "-w" for sub-makes, we don't want that 0087 QLatin1String noPrintDirFlag = QLatin1String(" --no-print-directory"); 0088 #else 0089 QLatin1String noPrintDirFlag; 0090 #endif 0091 return QLatin1String("make -k") + noPrintDirFlag + QLatin1String(" -W \'") + absoluteFile + QLatin1String("\' -W \'") + relativeFile + QLatin1String("\' -n ") + makeParameters; 0092 } 0093 0094 bool hasMakefile() const 0095 { 0096 QFileInfo makeFile(m_path, QStringLiteral("Makefile")); 0097 return makeFile.exists(); 0098 } 0099 0100 QStringList possibleTargets(const QString& targetBaseName) const 0101 { 0102 const QStringList ret{ 0103 ///@todo open the make-file, and read the target-names from there. 0104 //It would be nice if all targets could be processed in one call, the problem is the exit-status of make, so for now make has to be called multiple times. 0105 targetBaseName + QLatin1String(".o"), 0106 targetBaseName + QLatin1String(".lo"), 0107 //ret << targetBaseName + ".lo " + targetBaseName + ".o"; 0108 targetBaseName + QLatin1String(".ko"), 0109 }; 0110 return ret; 0111 } 0112 0113 private: 0114 QString m_path; 0115 }; 0116 0117 static void mergePaths(KDevelop::Path::List& destList, const KDevelop::Path::List& srcList) 0118 { 0119 for (const Path& path : srcList) { 0120 if (!destList.contains(path)) 0121 destList.append(path); 0122 } 0123 } 0124 0125 void PathResolutionResult::mergeWith(const PathResolutionResult& rhs) 0126 { 0127 mergePaths(paths, rhs.paths); 0128 mergePaths(frameworkDirectories, rhs.frameworkDirectories); 0129 includePathDependency += rhs.includePathDependency; 0130 for (auto it = rhs.defines.begin(), end = rhs.defines.end(); it != end; ++it) { 0131 defines.insert(it.key(), it.value()); 0132 } 0133 } 0134 0135 PathResolutionResult::PathResolutionResult(bool success, const QString& errorMessage, const QString& longErrorMessage) 0136 : success(success) 0137 , errorMessage(errorMessage) 0138 , longErrorMessage(longErrorMessage) 0139 {} 0140 0141 PathResolutionResult::operator bool() const 0142 { 0143 return success; 0144 } 0145 0146 ModificationRevisionSet MakeFileResolver::findIncludePathDependency(const QString& file) 0147 { 0148 QString oldSourceDir = m_source; 0149 QString oldBuildDir = m_build; 0150 0151 Path currentWd(mapToBuild(file)); 0152 0153 ModificationRevisionSet rev; 0154 while (currentWd.hasParent()) { 0155 currentWd = currentWd.parent(); 0156 QString path = currentWd.toLocalFile(); 0157 QFileInfo makeFile(QDir(path), QStringLiteral("Makefile")); 0158 if (makeFile.exists()) { 0159 IndexedString makeFileStr(makeFile.filePath()); 0160 rev.addModificationRevision(makeFileStr, ModificationRevision::revisionForFile(makeFileStr)); 0161 break; 0162 } 0163 } 0164 0165 setOutOfSourceBuildSystem(oldSourceDir, oldBuildDir); 0166 0167 return rev; 0168 } 0169 0170 bool MakeFileResolver::executeCommand(const QString& command, const QString& workingDirectory, QString& result) const 0171 { 0172 ifTest(cout << "executing " << command.toUtf8().constData() << endl); 0173 ifTest(cout << "in " << workingDirectory.toUtf8().constData() << endl); 0174 0175 KProcess proc; 0176 proc.setWorkingDirectory(workingDirectory); 0177 proc.setOutputChannelMode(KProcess::MergedChannels); 0178 0179 QStringList args(command.split(QLatin1Char(' '))); 0180 QString prog = args.takeFirst(); 0181 proc.setProgram(prog, args); 0182 0183 int status = proc.execute(processTimeoutSeconds * 1000); 0184 result = QString::fromUtf8(proc.readAll()); 0185 0186 return status == 0; 0187 } 0188 0189 MakeFileResolver::MakeFileResolver() 0190 0191 { 0192 } 0193 0194 ///More efficient solution: Only do exactly one call for each directory. During that call, mark all source-files as changed, and make all targets for those files. 0195 PathResolutionResult MakeFileResolver::resolveIncludePath(const QString& file) 0196 { 0197 if (file.isEmpty()) { 0198 // for unit tests with temporary files 0199 return PathResolutionResult(); 0200 } 0201 0202 QFileInfo fi(file); 0203 return resolveIncludePath(fi.fileName(), fi.absolutePath()); 0204 } 0205 0206 QString MakeFileResolver::mapToBuild(const QString &path) const 0207 { 0208 QString wd = QDir::cleanPath(path); 0209 if (m_outOfSource) { 0210 if (wd.startsWith(m_source) && !wd.startsWith(m_build)) { 0211 //Move the current working-directory out of source, into the build-system 0212 wd = QDir::cleanPath(m_build + QLatin1Char('/') + wd.midRef(m_source.length())); 0213 } 0214 } 0215 return wd; 0216 } 0217 0218 void MakeFileResolver::clearCache() 0219 { 0220 QMutexLocker l(&s_cacheMutex); 0221 s_cache.clear(); 0222 } 0223 0224 PathResolutionResult MakeFileResolver::resolveIncludePath(const QString& file, const QString& _workingDirectory, int maxStepsUp) 0225 { 0226 //Prefer this result when returning a "fail". The include-paths of this result will always be added. 0227 PathResolutionResult resultOnFail; 0228 0229 if (m_isResolving) 0230 return PathResolutionResult(false, i18n("Tried include path resolution while another resolution process was still running")); 0231 0232 //Make the working-directory absolute 0233 QString workingDirectory = _workingDirectory; 0234 0235 if (QFileInfo(workingDirectory).isRelative()) { 0236 QUrl u = QUrl::fromLocalFile(QDir::currentPath()); 0237 0238 if (workingDirectory == QLatin1String(".")) 0239 workingDirectory = QString(); 0240 else if (workingDirectory.startsWith(QLatin1String("./"))) 0241 workingDirectory.remove(0, 2); 0242 0243 if (!workingDirectory.isEmpty()) { 0244 u = u.adjusted(QUrl::StripTrailingSlash); 0245 u.setPath(u.path() + QLatin1Char('/') + workingDirectory); 0246 } 0247 workingDirectory = u.toLocalFile(); 0248 } else 0249 workingDirectory = _workingDirectory; 0250 0251 ifTest(cout << "working-directory: " << workingDirectory.toLocal8Bit().data() << " file: " << file.toLocal8Bit().data() << std::endl;) 0252 0253 QDir sourceDir(workingDirectory); 0254 QDir dir = QDir(mapToBuild(sourceDir.absolutePath())); 0255 0256 QFileInfo makeFile(dir, QStringLiteral("Makefile")); 0257 if (!makeFile.exists()) { 0258 if (maxStepsUp > 0) { 0259 //If there is no makefile in this directory, go one up and re-try from there 0260 QFileInfo fileName(file); 0261 QString localName = sourceDir.dirName(); 0262 0263 if (sourceDir.cdUp() && !fileName.isAbsolute()) { 0264 const QString checkFor = localName + QLatin1Char('/') + file; 0265 PathResolutionResult oneUp = resolveIncludePath(checkFor, sourceDir.path(), maxStepsUp-1); 0266 if (oneUp.success) { 0267 oneUp.mergeWith(resultOnFail); 0268 return oneUp; 0269 } 0270 } 0271 } 0272 0273 if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty()) 0274 return resultOnFail; 0275 else 0276 return PathResolutionResult(false, i18n("Makefile is missing in folder \"%1\"", dir.absolutePath()), i18n("Problem while trying to resolve include paths for %1", file)); 0277 } 0278 0279 PushValue<bool> e(m_isResolving, true); 0280 0281 Path::List cachedPaths; //If the call doesn't succeed, use the cached not up-to-date version 0282 Path::List cachedFWDirs; 0283 QHash<QString, QString> cachedDefines; 0284 ModificationRevisionSet dependency; 0285 dependency.addModificationRevision(IndexedString(makeFile.filePath()), ModificationRevision::revisionForFile(IndexedString(makeFile.filePath()))); 0286 dependency += resultOnFail.includePathDependency; 0287 Cache::iterator it; 0288 { 0289 QMutexLocker l(&s_cacheMutex); 0290 it = s_cache.find(dir.path()); 0291 if (it != s_cache.end()) { 0292 cachedPaths = it->paths; 0293 cachedFWDirs = it->frameworkDirectories; 0294 cachedDefines = it->defines; 0295 if (dependency == it->modificationTime) { 0296 if (!it->failed) { 0297 //We have a valid cached result 0298 PathResolutionResult ret(true); 0299 ret.paths = it->paths; 0300 ret.frameworkDirectories = it->frameworkDirectories; 0301 ret.defines = it->defines; 0302 ret.mergeWith(resultOnFail); 0303 return ret; 0304 } else { 0305 //We have a cached failed result. We should use that for some time but then try again. Return the failed result if: (there were too many tries within this folder OR this file was already tried) AND The last tries have not expired yet 0306 if (/*(it->failedFiles.size() > 3 || it->failedFiles.find(file) != it->failedFiles.end()) &&*/ it->failTime 0307 .secsTo(QDateTime::currentDateTimeUtc()) 0308 < CACHE_FAIL_FOR_SECONDS) { 0309 PathResolutionResult ret(false); // Fake that the result is ok 0310 ret.errorMessage = i18n("Cached: %1", it->errorMessage); 0311 ret.longErrorMessage = it->longErrorMessage; 0312 ret.paths = it->paths; 0313 ret.frameworkDirectories = it->frameworkDirectories; 0314 ret.defines = it->defines; 0315 ret.mergeWith(resultOnFail); 0316 return ret; 0317 } else { 0318 // Try getting a correct result again 0319 } 0320 } 0321 } 0322 } 0323 } 0324 0325 ///STEP 1: Prepare paths 0326 QString targetName; 0327 QFileInfo fi(file); 0328 0329 QString absoluteFile = file; 0330 if (fi.isRelative()) 0331 absoluteFile = workingDirectory + QLatin1Char('/') + file; 0332 absoluteFile = QDir::cleanPath(absoluteFile); 0333 0334 int dot; 0335 if ((dot = file.lastIndexOf(QLatin1Char('.'))) == -1) { 0336 if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty()) 0337 return resultOnFail; 0338 else 0339 return PathResolutionResult(false, i18n("Filename %1 seems to be malformed", file)); 0340 } 0341 0342 targetName = file.left(dot); 0343 0344 QString wd = dir.path(); 0345 if (QFileInfo(wd).isRelative()) { 0346 wd = QDir::cleanPath(QDir::currentPath() + QLatin1Char('/') + wd); 0347 } 0348 0349 wd = mapToBuild(wd); 0350 0351 SourcePathInformation source(wd); 0352 QStringList possibleTargets = source.possibleTargets(targetName); 0353 0354 ///STEP 3: Try resolving the paths, by using once the absolute and once the relative file-path. Which kind is required differs from setup to setup. 0355 0356 ///STEP 3.1: Try resolution using the absolute path 0357 PathResolutionResult res; 0358 //Try for each possible target 0359 res = resolveIncludePathInternal(absoluteFile, wd, possibleTargets.join(QLatin1Char(' ')), source, maximumInternalResolutionDepth); 0360 if (!res) { 0361 ifTest(cout << "Try for absolute file " << absoluteFile.toLocal8Bit().data() << " and targets " << possibleTargets.join(", ").toLocal8Bit().data() 0362 << " failed: " << res.longErrorMessage.toLocal8Bit().data() << endl;) 0363 } 0364 0365 res.includePathDependency = dependency; 0366 0367 if (res.paths.isEmpty()) { 0368 res.paths = cachedPaths; //We failed, maybe there is an old cached result, use that. 0369 res.defines = cachedDefines; 0370 } 0371 // a build command could contain only one or more -iframework or -F specifications. 0372 if (res.frameworkDirectories.isEmpty()) { 0373 res.frameworkDirectories = cachedFWDirs; 0374 } 0375 0376 { 0377 QMutexLocker l(&s_cacheMutex); 0378 if (it == s_cache.end()) 0379 it = s_cache.insert(dir.path(), CacheEntry()); 0380 0381 CacheEntry& ce(*it); 0382 ce.paths = res.paths; 0383 ce.frameworkDirectories = res.frameworkDirectories; 0384 ce.modificationTime = dependency; 0385 0386 if (!res) { 0387 ce.failed = true; 0388 ce.errorMessage = res.errorMessage; 0389 ce.longErrorMessage = res.longErrorMessage; 0390 ce.failTime = QDateTime::currentDateTimeUtc(); 0391 ce.failedFiles[file] = true; 0392 } else { 0393 ce.failed = false; 0394 ce.failedFiles.clear(); 0395 } 0396 } 0397 0398 0399 if (!res && (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty())) 0400 return resultOnFail; 0401 0402 return res; 0403 } 0404 0405 static QRegularExpression includeRegularExpression() 0406 { 0407 static const QRegularExpression expression(QStringLiteral( 0408 "\\s(--include-dir=|-I\\s*|-isystem\\s+|-iframework\\s+|-F\\s*)(" 0409 "\\'.*\\'|\\\".*\\\"" //Matches "hello", 'hello', 'hello"hallo"', etc. 0410 "|" 0411 "((?:\\\\.)?([\\S^\\\\]?))+" //Matches /usr/I\ am\ a\ strange\ path/include 0412 ")(?=\\s)" 0413 )); 0414 Q_ASSERT(expression.isValid()); 0415 return expression; 0416 } 0417 0418 PathResolutionResult MakeFileResolver::resolveIncludePathInternal(const QString& file, const QString& workingDirectory, 0419 const QString& makeParameters, const SourcePathInformation& source, 0420 int maxDepth) 0421 { 0422 --maxDepth; 0423 if (maxDepth < 0) 0424 return PathResolutionResult(false); 0425 0426 QString fullOutput; 0427 executeCommand(source.createCommand(file, workingDirectory, makeParameters), workingDirectory, fullOutput); 0428 0429 { 0430 QRegExp newLineRx(QStringLiteral("\\\\\\n")); 0431 fullOutput.remove(newLineRx); 0432 } 0433 ///@todo collect multiple outputs at the same time for performance-reasons 0434 QString firstLine = fullOutput; 0435 int lineEnd; 0436 if ((lineEnd = fullOutput.indexOf(QLatin1Char('\n'))) != -1) 0437 firstLine.truncate(lineEnd); //Only look at the first line of output 0438 0439 /** 0440 * There's two possible cases this can currently handle. 0441 * 1.: gcc is called, with the parameters we are searching for (so we parse the parameters) 0442 * 2.: A recursive make is called, within another directory(so we follow the recursion and try again) "cd /foo/bar && make -f pi/pa/build.make pi/pa/po.o 0443 * */ 0444 0445 ///STEP 1: Test if it is a recursive make-call 0446 // Do not search for recursive make-calls if we already have include-paths available. Happens in kernel modules. 0447 if (!includeRegularExpression().match(fullOutput).hasMatch()) { 0448 QRegExp makeRx(QStringLiteral("\\bmake\\s")); 0449 int offset = 0; 0450 while ((offset = makeRx.indexIn(firstLine, offset)) != -1) { 0451 QString prefix = firstLine.leftRef(offset).trimmed().toString(); 0452 if (prefix.endsWith(QLatin1String("&&")) || prefix.endsWith(QLatin1Char(';')) || prefix.isEmpty()) { 0453 QString newWorkingDirectory = workingDirectory; 0454 ///Extract the new working-directory 0455 if (!prefix.isEmpty()) { 0456 if (prefix.endsWith(QLatin1String("&&"))) 0457 prefix.chop(2); 0458 else if (prefix.endsWith(QLatin1Char(';'))) 0459 prefix.chop(1); 0460 0461 ///Now test if what we have as prefix is a simple "cd /foo/bar" call. 0462 0463 //In cases like "cd /media/data/kdedev/4.0/build/kdevelop && cd /media/data/kdedev/4.0/build/kdevelop" 0464 //We use the second directory. For t hat reason we search for the last index of "cd " 0465 int cdIndex = prefix.lastIndexOf(QLatin1String("cd ")); 0466 if (cdIndex != -1) { 0467 newWorkingDirectory = prefix.midRef(cdIndex + 3).trimmed().toString(); 0468 if (QFileInfo(newWorkingDirectory).isRelative()) 0469 newWorkingDirectory = workingDirectory + QLatin1Char('/') + newWorkingDirectory; 0470 newWorkingDirectory = QDir::cleanPath(newWorkingDirectory); 0471 } 0472 } 0473 0474 if (newWorkingDirectory == workingDirectory) { 0475 return PathResolutionResult(false, i18n("Failed to extract new working directory"), i18n("Output was: %1", fullOutput)); 0476 } 0477 0478 QFileInfo d(newWorkingDirectory); 0479 if (d.exists()) { 0480 ///The recursive working-directory exists. 0481 QString makeParams = firstLine.mid(offset+5); 0482 if (!makeParams.contains(QLatin1Char(';')) && !makeParams.contains(QLatin1String("&&"))) { 0483 ///Looks like valid parameters 0484 ///Make the file-name absolute, so it can be referenced from any directory 0485 QString absoluteFile = file; 0486 if (QFileInfo(absoluteFile).isRelative()) 0487 absoluteFile = workingDirectory + QLatin1Char('/') + file; 0488 Path absolutePath(absoluteFile); 0489 ///Try once with absolute, and if that fails with relative path of the file 0490 SourcePathInformation newSource(newWorkingDirectory); 0491 PathResolutionResult res = resolveIncludePathInternal(absolutePath.toLocalFile(), newWorkingDirectory, makeParams, newSource, maxDepth); 0492 if (res) 0493 return res; 0494 0495 return resolveIncludePathInternal(Path(newWorkingDirectory).relativePath(absolutePath), newWorkingDirectory, makeParams , newSource, maxDepth); 0496 }else{ 0497 return PathResolutionResult(false, i18n("Recursive make call failed"), i18n("The parameter string \"%1\" does not seem to be valid. Output was: %2.", makeParams, fullOutput)); 0498 } 0499 } else { 0500 return PathResolutionResult(false, i18n("Recursive make call failed"), i18n("The directory \"%1\" does not exist. Output was: %2.", newWorkingDirectory, fullOutput)); 0501 } 0502 0503 } else { 0504 return PathResolutionResult(false, i18n("Malformed recursive make call"), i18n("Output was: %1", fullOutput)); 0505 } 0506 0507 ++offset; 0508 if (offset >= firstLine.length()) break; 0509 } 0510 } 0511 0512 ///STEP 2: Search the output for include-paths 0513 0514 PathResolutionResult ret = processOutput(fullOutput, workingDirectory); 0515 if (ret.paths.isEmpty() && ret.frameworkDirectories.isEmpty()) 0516 return PathResolutionResult(false, i18n("Could not extract include paths from make output"), 0517 i18n("Folder: \"%1\" Command: \"%2\" Output: \"%3\"", workingDirectory, 0518 source.createCommand(file, workingDirectory, makeParameters), fullOutput)); 0519 return ret; 0520 } 0521 0522 QRegularExpression MakeFileResolver::defineRegularExpression() 0523 { 0524 static const QRegularExpression pattern( 0525 QStringLiteral("-(D|U)([^\\s=]+)(?:=(?:\"(.*?)(?<!\\\\)\"|([^\\s]*)))?") 0526 ); 0527 Q_ASSERT(pattern.isValid()); 0528 return pattern; 0529 } 0530 0531 static QString unescape(const QStringRef& input) 0532 { 0533 QString output; 0534 output.reserve(input.length()); 0535 bool isEscaped = false; 0536 for (const QChar c : input) { 0537 if (!isEscaped && c == QLatin1Char('\\')) { 0538 isEscaped = true; 0539 } else { 0540 output.append(c); 0541 isEscaped = false; 0542 } 0543 } 0544 return output; 0545 } 0546 0547 QHash<QString, QString> MakeFileResolver::extractDefinesFromCompileFlags(const QString& compileFlags, StringInterner& stringInterner, QHash<QString, QString> defines) 0548 { 0549 const auto& defineRx = defineRegularExpression(); 0550 auto it = defineRx.globalMatch(compileFlags); 0551 while (it.hasNext()) { 0552 const auto match = it.next(); 0553 const auto isUndefine = match.capturedRef(1) == QLatin1String("U"); 0554 const auto key = stringInterner.internString(match.captured(2)); 0555 if (isUndefine) { 0556 defines.remove(key); 0557 continue; 0558 } 0559 QString value; 0560 if (match.lastCapturedIndex() > 2) { 0561 value = unescape(match.capturedRef(match.lastCapturedIndex())); 0562 } 0563 defines[key] = stringInterner.internString(value); 0564 } 0565 return defines; 0566 } 0567 0568 PathResolutionResult MakeFileResolver::processOutput(const QString& fullOutput, const QString& workingDirectory) const 0569 { 0570 PathResolutionResult ret(true); 0571 ret.longErrorMessage = fullOutput; 0572 ifTest(cout << "full output: " << qPrintable(fullOutput) << endl); 0573 0574 { 0575 const auto& includeRx = includeRegularExpression(); 0576 auto it = includeRx.globalMatch(fullOutput); 0577 while (it.hasNext()) { 0578 const auto match = it.next(); 0579 QString path = match.captured(2); 0580 if (path.startsWith(QLatin1Char('"')) || (path.startsWith(QLatin1Char('\'')) && path.length() > 2)) { 0581 //probable a quoted path 0582 if (path.endsWith(path.leftRef(1))) { 0583 //Quotation is ok, remove it 0584 path = path.mid(1, path.length() - 2); 0585 } 0586 } 0587 if (QDir::isRelativePath(path)) 0588 path = workingDirectory + QLatin1Char('/') + path; 0589 const auto& internedPath = m_pathInterner.internPath(path); 0590 const auto type = match.capturedRef(1); 0591 const auto isFramework = type.startsWith(QLatin1String("-iframework")) 0592 || type.startsWith(QLatin1String("-F")); 0593 if (isFramework) { 0594 ret.frameworkDirectories << internedPath; 0595 } else { 0596 ret.paths << internedPath; 0597 } 0598 } 0599 } 0600 0601 ret.defines = extractDefinesFromCompileFlags(fullOutput, m_stringInterner, ret.defines); 0602 0603 return ret; 0604 } 0605 0606 void MakeFileResolver::resetOutOfSourceBuild() 0607 { 0608 m_outOfSource = false; 0609 } 0610 0611 void MakeFileResolver::setOutOfSourceBuildSystem(const QString& source, const QString& build) 0612 { 0613 if (source == build) { 0614 resetOutOfSourceBuild(); 0615 return; 0616 } 0617 m_outOfSource = true; 0618 m_source = QDir::cleanPath(source); 0619 m_build = QDir::cleanPath(m_build); 0620 } 0621 0622 PathInterner::PathInterner(const Path& base) 0623 : m_base(base) 0624 { 0625 } 0626 0627 Path PathInterner::internPath(const QString& path) 0628 { 0629 Path& ret = m_pathCache[path]; 0630 if (ret.isEmpty() != path.isEmpty()) { 0631 ret = Path(m_base, path); 0632 } 0633 return ret; 0634 } 0635 0636 QString StringInterner::internString(const QString& path) 0637 { 0638 auto it = m_stringCache.constFind(path); 0639 if (it != m_stringCache.constEnd()) { 0640 return *it; 0641 } 0642 m_stringCache.insert(path); 0643 return path; 0644 }