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 }