/* * KDevelop C++ Language Support * * Copyright 2007 David Nolden * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /** * Compatibility: * make/automake: Should work perfectly * cmake: Thanks to the path-recursion, this works with cmake(tested with version "2.4-patch 6" * with kdelibs out-of-source and with kdevelop4 in-source) * * unsermake: * unsermake is detected by reading the first line of the makefile. If it contains * "generated by unsermake" the following things are respected: * 1. Since unsermake does not have the -W command (which should tell it to recompile * the given file no matter whether it has been changed or not), the file-modification-time of * the file is changed temporarily and the --no-real-compare option is used to force recompilation. * 2. The targets seem to be called *.lo instead of *.o when using unsermake, so *.lo names are used. * example-(test)command: unsermake --no-real-compare -n myfile.lo **/ #include "includepathresolver.h" #include #include #include #include #include #include #include #ifdef TEST #include #endif #include #include #include #include #include #include #include #include //#define VERBOSE #if defined(TEST) || defined(VERBOSE) #define ifTest(x) x #else #define ifTest(x) #endif const int maximumInternalResolutionDepth = 3; using namespace std; using namespace KDevelop; namespace { ///After how many seconds should we retry? static const int CACHE_FAIL_FOR_SECONDS = 200; static const int processTimeoutSeconds = 30; struct CacheEntry { CacheEntry() : failed(false) { } ModificationRevisionSet modificationTime; QStringList paths; QString errorMessage, longErrorMessage; bool failed; QMap failedFiles; QDateTime failTime; }; typedef QMap Cache; static Cache s_cache; static QMutex s_cacheMutex; } namespace CppTools { ///Helper-class used to fake file-modification times class FileModificationTimeWrapper { public: ///@param files list of files that should be fake-modified(modtime will be set to current time) explicit FileModificationTimeWrapper(const QStringList& files = QStringList(), const QString& workingDirectory = QString()) : m_newTime(time(0)) { for (QStringList::const_iterator it = files.constBegin(); it != files.constEnd(); ++it) { ifTest(cout << "touching " << it->toUtf8().constData() << endl); QFileInfo fileinfo(QDir(workingDirectory), *it); if (!fileinfo.exists()) { cout << "File does not exist: " << it->toUtf8().constData() << "in working dir " << QDir::currentPath().toUtf8().constData() << "\n"; continue; } QString filename = fileinfo.canonicalFilePath(); if (m_stat.find(filename) != m_stat.end()) { cout << "Duplicate file: " << filename.toUtf8().constData() << endl; continue; } QByteArray bFileName = filename.toLocal8Bit(); const char* bFileNameC = bFileName.constData(); struct stat s; if (stat(bFileNameC, &s) == 0) { ///Success m_stat[filename] = s.st_mtime; ///change the modification-time to m_newTime struct timeval times[2]; times[0].tv_sec = m_newTime; times[0].tv_usec = 0; times[1].tv_sec = m_newTime; times[1].tv_usec = 0; if (utimes(bFileNameC, times) != 0) { ifTest(cout << "failed to touch " << it->toUtf8().constData() << endl); } } } } ///Undo changed modification-times void unModify() { for (QHash::const_iterator it = m_stat.constBegin(); it != m_stat.constEnd(); ++it) { ifTest(cout << "untouching " << it.key().toUtf8().constData() << endl); QByteArray bFileName = it.key().toLocal8Bit(); const char* bFileNameC = bFileName.constData(); struct stat s; if (stat(bFileNameC, &s) == 0) { if (s.st_mtime == m_newTime) { ///Still the modtime that we've set, change it back struct timeval times[2]; times[0].tv_usec = 0; times[0].tv_sec = s.st_atime; times[1].tv_usec = 0; times[1].tv_sec = *it; if (utimes(bFileNameC, times) != 0) { perror("Resetting modification time"); ifTest(cout << "failed to untouch " << it.key().toUtf8().constData() << endl); } } else { ///The file was modified since we changed the modtime ifTest(cout << "will not untouch " << it.key().toUtf8().constData() << " because the modification-time has changed" << endl); } } else { perror("File status"); } } } ~FileModificationTimeWrapper() { unModify(); } private: QHash m_stat; time_t m_newTime; }; class SourcePathInformation { public: SourcePathInformation(const QString& path) : m_path(path) , m_isUnsermake(false) , m_shouldTouchFiles(false) { m_isUnsermake = isUnsermakePrivate(path); ifTest(if (m_isUnsermake) cout << "unsermake detected" << endl); } bool isUnsermake() const { return m_isUnsermake; } ///When this is set, the file-modification times are changed no matter whether it is unsermake or make void setShouldTouchFiles(bool b) { m_shouldTouchFiles = b; } QString getCommand(const QString& absoluteFile, const QString& workingDirectory, const QString& makeParameters) const { if (isUnsermake()) { return "unsermake -k --no-real-compare -n " + makeParameters; } else { QString relativeFile = KUrl::relativePath(workingDirectory, absoluteFile); return "make -k --no-print-directory -W \'" + absoluteFile + "\' -W \'" + relativeFile + "\' -n " + makeParameters; } } bool hasMakefile() const { QFileInfo makeFile(m_path, "Makefile"); return makeFile.exists(); } bool shouldTouchFiles() const { return isUnsermake() || m_shouldTouchFiles; } QStringList possibleTargets(const QString& targetBaseName) const { QStringList ret; ///@todo open the make-file, and read the target-names from there. if (isUnsermake()) { //unsermake breaks if the first given target does not exist, so in worst-case 2 calls are necessary ret << targetBaseName + ".lo"; ret << targetBaseName + ".o"; } else { //It would be nice if both targets could be processed in one call, the problem is the exit-status of make, so for now make has to be called twice. ret << targetBaseName + ".o"; ret << targetBaseName + ".lo"; //ret << targetBaseName + ".lo " + targetBaseName + ".o"; } ret << targetBaseName + ".ko"; return ret; } private: bool isUnsermakePrivate(const QString& path) { bool ret = false; QFileInfo makeFile(path, "Makefile"); QFile f(makeFile.absoluteFilePath()); if (f.open(QIODevice::ReadOnly)) { QString firstLine = f.readLine(128); if (firstLine.indexOf("generated by unsermake") != -1) { ret = true; } f.close(); } return ret; } QString m_path; bool m_isUnsermake; bool m_shouldTouchFiles; }; } using namespace CppTools; bool CustomIncludePathsSettings::isValid() const { return !storagePath.isEmpty(); } bool CppTools::CustomIncludePathsSettings::delete_() { QString file = storagePath + "/.kdev_include_paths"; return QFile::remove(file); } ModificationRevisionSet IncludePathResolver::findIncludePathDependency(const QString& file) { ModificationRevisionSet rev; CppTools::CustomIncludePathsSettings settings = CustomIncludePathsSettings::findAndReadAbsolute(file); IndexedString storageFile(settings.storageFile()); if (!storageFile.isEmpty()) rev.addModificationRevision(storageFile, ModificationRevision::revisionForFile(storageFile)); QString oldSourceDir = m_source; QString oldBuildDir = m_build; if (!settings.buildDir.isEmpty() && !settings.sourceDir.isEmpty()) setOutOfSourceBuildSystem(settings.sourceDir, settings.buildDir); KUrl currentWd = mapToBuild(KUrl(file)); while (!currentWd.path().isEmpty()) { if (currentWd == currentWd.upUrl()) break; currentWd = currentWd.upUrl(); QString path = currentWd.toLocalFile(); QFileInfo makeFile(QDir(path), "Makefile"); if (makeFile.exists()) { IndexedString makeFileStr(makeFile.filePath()); rev.addModificationRevision(makeFileStr, ModificationRevision::revisionForFile(makeFileStr)); break; } } setOutOfSourceBuildSystem(oldSourceDir, oldBuildDir); return rev; } QString CppTools::CustomIncludePathsSettings::find(const QString& startPath) { QDir dir(startPath); static const QString pathFile(".kdev_include_paths"); while (dir.exists()) { QFileInfo customIncludePaths(dir, pathFile); if (customIncludePaths.exists()) return customIncludePaths.absoluteFilePath(); if (!dir.cdUp()) break; } return QString(); } CppTools::CustomIncludePathsSettings CppTools::CustomIncludePathsSettings::findAndRead(const QString& current) { QString file = find(current); if (file.isEmpty()) return CppTools::CustomIncludePathsSettings(); KUrl fileUrl(file); fileUrl.setFileName(QString()); return read(fileUrl.toLocalFile()); } QString CppTools::CustomIncludePathsSettings::storageFile() const { if (storagePath.isEmpty()) return QString(); QDir dir(storagePath); return dir.filePath(".kdev_include_paths"); } CppTools::CustomIncludePathsSettings CppTools::CustomIncludePathsSettings::findAndReadAbsolute(const QString& startPath) { CppTools::CustomIncludePathsSettings settings(findAndRead(startPath)); QDir sourceDir(settings.storagePath); // Turn relative paths into absolute paths from the storage path for (int i = 0; i < settings.paths.size(); i++) { const QString& path = settings.paths[i]; if (!path.startsWith('/')) settings.paths[i] = sourceDir.absoluteFilePath(path); } return settings; } CustomIncludePathsSettings CustomIncludePathsSettings::read(const QString& storagePath) { QDir sourceDir(storagePath); CustomIncludePathsSettings ret; ///If there is a .kdev_include_paths file, use it. // #ifdef READ_CUSTOM_INCLUDE_PATHS QFileInfo customIncludePaths(sourceDir, ".kdev_include_paths"); if (customIncludePaths.exists()) { QFile f(customIncludePaths.filePath()); if (f.open(QIODevice::ReadOnly | QIODevice::Text)) { ret.storagePath = storagePath; QString read = QString::fromLocal8Bit(f.readAll()); QStringList lines = read.split('\n', QString::SkipEmptyParts); foreach (const QString& line, lines) { if (!line.isEmpty()) { QString textLine = line; if (textLine.startsWith("RESOLVE:")) { ifTest(std::cout << "found resolve line: " << textLine.toLocal8Bit().data() << std::endl;) //Resolve using the build- and source-directory specified in the file int sourceIndex = textLine.indexOf(" SOURCE="); if (sourceIndex != -1) { ifTest(std::cout << "found source specification" << std::endl;) int buildIndex = textLine.indexOf(" BUILD=", sourceIndex); if (buildIndex != -1) { ifTest(std::cout << "found build specification" << std::endl;) int sourceStart = sourceIndex+8; QString sourceDir = textLine.mid(sourceStart, buildIndex - sourceStart).trimmed(); int buildStart = buildIndex + 7; QString buildDir = textLine.mid(buildStart, textLine.length()-buildStart).trimmed(); ifTest(std::cout << "directories: " << sourceDir.toLocal8Bit().data() << " " << buildDir.toLocal8Bit().data() << std::endl;) ret.buildDir = buildDir; ret.sourceDir = sourceDir; } } }else{ ret.paths << textLine; } } } f.close(); } } return ret; } bool CppTools::CustomIncludePathsSettings::write() { QDir source(storagePath); QFileInfo customIncludePaths(source, ".kdev_include_paths"); QFile f(customIncludePaths.filePath()); if (f.open(QIODevice::WriteOnly | QIODevice::Text)) { if (buildDir != sourceDir) { f.write("RESOLVE: SOURCE="); f.write(sourceDir.toLocal8Bit()); f.write(" BUILD="); f.write(buildDir.toLocal8Bit()); f.write("\n"); } foreach (const QString& customPath, paths) { f.write(customPath.toLocal8Bit()); f.write("\n"); } return true; }else{ return false; } } bool IncludePathResolver::executeCommand(const QString& command, const QString& workingDirectory, QString& result) const { ifTest(cout << "executing " << command.toUtf8().constData() << endl); ifTest(cout << "in " << workingDirectory.toUtf8().constData() << endl); KProcess proc; proc.setWorkingDirectory(workingDirectory); proc.setOutputChannelMode(KProcess::MergedChannels); QStringList args(command.split(' ')); QString prog = args.takeFirst(); proc.setProgram(prog, args); int status = proc.execute(processTimeoutSeconds * 1000); result = proc.readAll(); return status == 0; } IncludePathResolver::IncludePathResolver() : m_isResolving(false) , m_outOfSource(false) , m_enableMakeResolution(true) { } void IncludePathResolver::enableMakeResolution(bool enable) { m_enableMakeResolution = enable; } ///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. PathResolutionResult IncludePathResolver::resolveIncludePath(const QString& file) { if (file.isEmpty()) { // for unit tests with temporary files return PathResolutionResult(); } QFileInfo fi(file); return resolveIncludePath(fi.fileName(), fi.absolutePath()); } KUrl IncludePathResolver::mapToBuild(const KUrl& url) { KUrl wdUrl = url; wdUrl.cleanPath(); QString wd = wdUrl.toLocalFile(KUrl::RemoveTrailingSlash); if (m_outOfSource) { if (wd.startsWith(m_source) && !wd.startsWith(m_build)) { //Move the current working-directory out of source, into the build-system wd = m_build + '/' + wd.mid(m_source.length()); KUrl u(wd); u.cleanPath(); wd = u.toLocalFile(); } } return KUrl(wd); } void CppTools::IncludePathResolver::clearCache() { QMutexLocker l(&s_cacheMutex); s_cache.clear(); } PathResolutionResult IncludePathResolver::resolveIncludePath(const QString& file, const QString& _workingDirectory, int maxStepsUp) { //Prefer this result when returning a "fail". The include-paths of this result will always be added. PathResolutionResult resultOnFail; if (m_isResolving) return PathResolutionResult(false, i18n("Tried include path resolution while another resolution process was still running")); //Make the working-directory absolute QString workingDirectory = _workingDirectory; if (KUrl(workingDirectory).isRelative()) { KUrl u(QDir::currentPath()); if (workingDirectory == ".") workingDirectory = QString(); else if (workingDirectory.startsWith("./")) workingDirectory = workingDirectory.mid(2); if (!workingDirectory.isEmpty()) u.addPath(workingDirectory); workingDirectory = u.toLocalFile(); } else workingDirectory = _workingDirectory; ifTest(cout << "working-directory: " << workingDirectory.toLocal8Bit().data() << " file: " << file.toLocal8Bit().data() << std::endl;) CppTools::CustomIncludePathsSettings customPaths = CustomIncludePathsSettings::findAndReadAbsolute(workingDirectory); if (customPaths.isValid()) { PathResolutionResult result(true); if (!m_outOfSource && customPaths.buildDir != customPaths.sourceDir) { QString sourceDir = customPaths.sourceDir; QString buildDir = customPaths.buildDir; if (sourceDir != buildDir) { QString oldSourceDir = m_source; QString oldBuildDir = m_build; setOutOfSourceBuildSystem(sourceDir, buildDir); QStringList fileParts = file.split("/"); //Add all directories that were stepped up to the file again QString useFile = file; QString useWorkingDirectory = workingDirectory; if (fileParts.count() > 1) { useWorkingDirectory += "/" + QStringList(fileParts.mid(0, fileParts.size()-1)).join("/"); useFile = fileParts.last(); } ifTest(std::cout << "starting sub-resolver with " << useFile.toLocal8Bit().data() << " " << useWorkingDirectory.toLocal8Bit().data() << std::endl;) PathResolutionResult subResult = resolveIncludePath(useFile, useWorkingDirectory, maxStepsUp + fileParts.count() - 1); subResult.paths += result.paths; result = subResult; if (!result) { ifTest(std::cout << "problem in sub-resolver: " << subResult.errorMessage.toLocal8Bit().data() << std::endl;) } setOutOfSourceBuildSystem(oldSourceDir, oldBuildDir); } } result.paths += customPaths.paths; const IndexedString storageFile(customPaths.storageFile()); ModificationRevisionSet rev; rev.addModificationRevision(storageFile, ModificationRevision::revisionForFile(storageFile)); result.includePathDependency = rev; if (!m_enableMakeResolution) { return result; } resultOnFail = result; } QDir sourceDir(workingDirectory); QDir dir = QDir(mapToBuild(sourceDir.absolutePath()).toLocalFile()); QFileInfo makeFile(dir, "Makefile"); if (!makeFile.exists()) { if (maxStepsUp > 0) { //If there is no makefile in this directory, go one up and re-try from there QFileInfo fileName(file); QString localName = sourceDir.dirName(); if (sourceDir.cdUp() && !fileName.isAbsolute()) { QString checkFor = localName + "/" + file; PathResolutionResult oneUp = resolveIncludePath(checkFor, sourceDir.path(), maxStepsUp-1); if (oneUp.success) { oneUp.addPathsUnique(resultOnFail); return oneUp; } } } if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty()) return resultOnFail; else return PathResolutionResult(false, i18n("Makefile is missing in folder \"%1\"", dir.absolutePath()), i18n("Problem while trying to resolve include paths for %1", file)); } PushValue e(m_isResolving, true); QStringList cachedPaths; //If the call doesn't succeed, use the cached not up-to-date version ModificationRevisionSet dependency; dependency.addModificationRevision(IndexedString(makeFile.filePath()), ModificationRevision::revisionForFile(IndexedString(makeFile.filePath()))); dependency += resultOnFail.includePathDependency; Cache::iterator it; { QMutexLocker l(&s_cacheMutex); it = s_cache.find(dir.path()); if (it != s_cache.end()) { cachedPaths = (*it).paths; if (dependency == (*it).modificationTime) { if (!(*it).failed) { //We have a valid cached result PathResolutionResult ret(true); ret.paths = (*it).paths; ret.addPathsUnique(resultOnFail); return ret; } else { //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 if (/*((*it).failedFiles.size() > 3 || (*it).failedFiles.find(file) != (*it).failedFiles.end()) &&*/ (*it).failTime.secsTo(QDateTime::currentDateTime()) < CACHE_FAIL_FOR_SECONDS) { PathResolutionResult ret(false); //Fake that the result is ok ret.errorMessage = i18n("Cached: %1", (*it).errorMessage); ret.longErrorMessage = (*it).longErrorMessage; ret.paths = (*it).paths; ret.addPathsUnique(resultOnFail); return ret; } else { //Try getting a correct result again } } } } } ///STEP 1: Prepare paths QString targetName; QFileInfo fi(file); QString absoluteFile = file; if (KUrl(file).isRelative()) absoluteFile = workingDirectory + '/' + file; KUrl u(absoluteFile); u.cleanPath(); absoluteFile = u.toLocalFile(); int dot; if ((dot = file.lastIndexOf('.')) == -1) { if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty()) return resultOnFail; else return PathResolutionResult(false, i18n("Filename %1 seems to be malformed", file)); } targetName = file.left(dot); QString wd = dir.path(); if (KUrl(wd).isRelative()) { wd = QDir::currentPath() + '/' + wd; KUrl u(wd); u.cleanPath(); wd = u.toLocalFile(); } wd = mapToBuild(wd).toLocalFile(); SourcePathInformation source(wd); QStringList possibleTargets = source.possibleTargets(targetName); source.setShouldTouchFiles(true); //Think about whether this should be always enabled. I've enabled it for now so there's an even bigger chance that everything works. ///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. ///STEP 3.1: Try resolution using the absolute path PathResolutionResult res; //Try for each possible target res = resolveIncludePathInternal(absoluteFile, wd, possibleTargets.join(" "), source, maximumInternalResolutionDepth); if (!res) { ifTest(cout << "Try for absolute file " << absoluteFile.toLocal8Bit().data() << " and targets " << possibleTargets.join(", ").toLocal8Bit().data() << " failed: " << res.longErrorMessage.toLocal8Bit().data() << endl;) } res.includePathDependency = dependency; if (res.paths.isEmpty()) res.paths = cachedPaths; //We failed, maybe there is an old cached result, use that. { QMutexLocker l(&s_cacheMutex); if (it == s_cache.end()) it = s_cache.insert(dir.path(), CacheEntry()); CacheEntry& ce(*it); ce.paths = res.paths; ce.modificationTime = dependency; if (!res) { ce.failed = true; ce.errorMessage = res.errorMessage; ce.longErrorMessage = res.longErrorMessage; ce.failTime = QDateTime::currentDateTime(); ce.failedFiles[file] = true; } else { ce.failed = false; ce.failedFiles.clear(); } } if (!res && (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty())) return resultOnFail; return res; } PathResolutionResult IncludePathResolver::resolveIncludePathInternal(const QString& file, const QString& workingDirectory, const QString& makeParameters, const SourcePathInformation& source, int maxDepth) { --maxDepth; if (maxDepth < 0) return PathResolutionResult(false); QString processStdout; QStringList touchFiles; if (source.shouldTouchFiles()) { touchFiles << file; } FileModificationTimeWrapper touch(touchFiles, workingDirectory); QString fullOutput; executeCommand(source.getCommand(file, workingDirectory, makeParameters), workingDirectory, fullOutput); QString includeParameterRx("\\s(-I|--include-dir=|-I\\s)"); QString quotedRx("(\\').*(\\')|(\\\").*(\\\")"); //Matches "hello", 'hello', 'hello"hallo"', etc. QString escapedPathRx("(([^)(\"'\\s]*)(\\\\\\s)?)*"); //Matches /usr/I\ am \ a\ strange\ path/include QRegExp includeRx(QString("%1(%2|%3)(?=\\s)").arg(includeParameterRx).arg(quotedRx).arg(escapedPathRx)); includeRx.setMinimal(true); includeRx.setCaseSensitivity(Qt::CaseSensitive); QRegExp newLineRx("\\\\\\n"); fullOutput.replace(newLineRx, ""); ///@todo collect multiple outputs at the same time for performance-reasons QString firstLine = fullOutput; int lineEnd; if ((lineEnd = fullOutput.indexOf('\n')) != -1) firstLine.truncate(lineEnd); //Only look at the first line of output /** * There's two possible cases this can currently handle. * 1.: gcc is called, with the parameters we are searching for (so we parse the parameters) * 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 * */ ///STEP 1: Test if it is a recursive make-call // Do not search for recursive make-calls if we already have include-paths available. Happens in kernel modules. if (!fullOutput.contains(includeRx)) { QRegExp makeRx("\\bmake\\s"); int offset = 0; while ((offset = makeRx.indexIn(firstLine, offset)) != -1) { QString prefix = firstLine.left(offset).trimmed(); if (prefix.endsWith("&&") || prefix.endsWith(';') || prefix.isEmpty()) { QString newWorkingDirectory = workingDirectory; ///Extract the new working-directory if (!prefix.isEmpty()) { if (prefix.endsWith("&&")) prefix.truncate(prefix.length() - 2); else if (prefix.endsWith(';')) prefix.truncate(prefix.length() - 1); ///Now test if what we have as prefix is a simple "cd /foo/bar" call. //In cases like "cd /media/data/kdedev/4.0/build/kdevelop && cd /media/data/kdedev/4.0/build/kdevelop" //We use the second directory. For t hat reason we search for the last index of "cd " int cdIndex = prefix.lastIndexOf("cd "); if (cdIndex != -1) { newWorkingDirectory = prefix.right(prefix.length() - 3 - cdIndex).trimmed(); if (KUrl(newWorkingDirectory).isRelative()) newWorkingDirectory = workingDirectory + '/' + newWorkingDirectory; KUrl u(newWorkingDirectory); u.cleanPath(); newWorkingDirectory = u.toLocalFile(); } } if (newWorkingDirectory == workingDirectory) { return PathResolutionResult(false, i18n("Failed to extract new working directory"), i18n("Output was: %1", fullOutput)); } QFileInfo d(newWorkingDirectory); if (d.exists()) { ///The recursive working-directory exists. QString makeParams = firstLine.mid(offset+5); if (!makeParams.contains(';') && !makeParams.contains("&&")) { ///Looks like valid parameters ///Make the file-name absolute, so it can be referenced from any directory QString absoluteFile = file; if (KUrl(absoluteFile).isRelative()) absoluteFile = workingDirectory + '/' + file; KUrl u(absoluteFile); u.cleanPath(); ///Try once with absolute, and if that fails with relative path of the file SourcePathInformation newSource(newWorkingDirectory); PathResolutionResult res = resolveIncludePathInternal(u.toLocalFile(), newWorkingDirectory, makeParams, newSource, maxDepth); if (res) return res; return resolveIncludePathInternal(KUrl::relativePath(newWorkingDirectory, u.toLocalFile()), newWorkingDirectory, makeParams , newSource, maxDepth); }else{ return PathResolutionResult(false, i18n("Recursive make call failed"), i18n("The parameter string \"%1\" does not seem to be valid. Output was: %2.", makeParams, fullOutput)); } } else { return PathResolutionResult(false, i18n("Recursive make call failed"), i18n("The directory \"%1\" does not exist. Output was: %2.", newWorkingDirectory, fullOutput)); } } else { return PathResolutionResult(false, i18n("Malformed recursive make call"), i18n("Output was: %1", fullOutput)); } ++offset; if (offset >= firstLine.length()) break; } } ///STEP 2: Search the output for include-paths PathResolutionResult ret(true); ret.longErrorMessage = fullOutput; ifTest(cout << "full output" << fullOutput.toAscii().data() << endl); int offset = 0; while ((offset = includeRx.indexIn(fullOutput, offset)) != -1) { offset += 1; ///The previous white space int pathOffset = 2; if (fullOutput[offset+1] == '-') { ///Must be --include-dir=, with a length of 14 characters pathOffset = 14; } if (fullOutput.length() <= offset + pathOffset) break; if (fullOutput[offset+pathOffset].isSpace()) pathOffset++; int start = offset + pathOffset; int end = offset + includeRx.matchedLength(); QString path = fullOutput.mid(start, end-start).trimmed(); if (path.startsWith('"') || (path.startsWith('\'') && path.length() > 2)) { //probable a quoted path if (path.endsWith(path.left(1))) { //Quotation is ok, remove it path = path.mid(1, path.length() - 2); } } if (KUrl(path).isRelative()) path = workingDirectory + '/' + path; KUrl u(path); u.cleanPath(); ret.paths << u.toLocalFile(); offset = end-1; } if (ret.paths.isEmpty()) return PathResolutionResult(false, i18n("Could not extract include paths from make output"), i18n("Folder: \"%1\" Command: \"%2\" Output: \"%3\"", workingDirectory, source.getCommand(file, workingDirectory, makeParameters), fullOutput)); return ret; } void IncludePathResolver::resetOutOfSourceBuild() { m_outOfSource = false; } void IncludePathResolver::setOutOfSourceBuildSystem(const QString& source, const QString& build) { if (source == build) { resetOutOfSourceBuild(); return; } m_outOfSource = true; KUrl sourceUrl(source); sourceUrl.cleanPath(); m_source = sourceUrl.toLocalFile(KUrl::RemoveTrailingSlash); KUrl buildUrl(build); buildUrl.cleanPath(); m_build = buildUrl.toLocalFile(KUrl::RemoveTrailingSlash); } #ifdef TEST /** This can be used for testing and debugging the system. To compile it use * gcc includepathresolver.cpp -I /usr/share/qt3/include -I /usr/include/kde -I ../../lib/util -DTEST -lkdecore -g -o includepathresolver * */ int main(int argc, char **argv) { QApplication app(argc,argv); IncludePathResolver resolver; if (argc < 3) { cout << "params: 1. file-name, 2. working-directory [3. source-directory 4. build-directory]" << endl; return 1; } if (argc >= 5) { cout << "mapping" << argv[3] << "->" << argv[4] << endl; resolver.setOutOfSourceBuildSystem(argv[3], argv[4]); } PathResolutionResult res = resolver.resolveIncludePath(argv[1], argv[2]); cout << "success:" << res.success << "\n"; if (!res.success) { cout << "error-message: \n" << res.errorMessage.toLocal8Bit().data() << "\n"; cout << "long error-message: \n" << res.longErrorMessage.toLocal8Bit().data() << "\n"; } cout << "path: \n" << res.paths.join("\n").toLocal8Bit().data() << "\n"; return res.success; } #endif