diff --git a/plugins/cmake/cmakeedit.cpp b/plugins/cmake/cmakeedit.cpp index 122d114a0e..40116debf3 100644 --- a/plugins/cmake/cmakeedit.cpp +++ b/plugins/cmake/cmakeedit.cpp @@ -1,334 +1,334 @@ /* KDevelop CMake Support * * Copyright 2007-2013 Aleix Pol * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU 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. */ #include "cmakeedit.h" #include #include #include #include #include #include #include #include #include "cmakemodelitems.h" using namespace KDevelop; namespace CMakeEdit { void eatLeadingWhitespace(KTextEditor::Document* doc, KTextEditor::Range& eater, const KTextEditor::Range& bounds) { QString text = doc->text(KTextEditor::Range(bounds.start(), eater.start())); int newStartLine = eater.start().line(), pos = text.length() - 2; //pos = index before eater.start while (pos > 0) { if (text[pos] == '\n') --newStartLine; else if (!text[pos].isSpace()) { ++pos; break; } --pos; } int lastNewLinePos = text.lastIndexOf('\n', pos - 1); int newStartCol = lastNewLinePos == -1 ? eater.start().column() + pos : pos - lastNewLinePos - 1; eater.setStart(KTextEditor::Cursor(newStartLine, newStartCol)); } KTextEditor::Range rangeForText(KTextEditor::Document* doc, const KTextEditor::Range& r, const QString& name) { QString txt=doc->text(r); QRegExp match("([\\s]|^)(\\./)?"+QRegExp::escape(name)); int namepos = match.indexIn(txt); int length = match.cap(0).size(); if(namepos == -1) return KTextEditor::Range::invalid(); //QRegExp doesn't support lookbehind asserts, and \b isn't good enough //so either match "^" or match "\s" and then +1 here if (txt[namepos].isSpace()) { ++namepos; --length; } KTextEditor::Cursor c(r.start()); - c.setLine(c.line() + txt.left(namepos).count('\n')); + c.setLine(c.line() + txt.leftRef(namepos).count('\n')); int lastNewLinePos = txt.lastIndexOf('\n', namepos); if (lastNewLinePos < 0) c.setColumn(r.start().column() + namepos); else c.setColumn(namepos - lastNewLinePos - 1); return KTextEditor::Range(c, KTextEditor::Cursor(c.line(), c.column()+length)); } bool followUses(KTextEditor::Document* doc, RangeInRevision r, const QString& name, const QUrl &lists, bool add, const QString& replace) { bool ret=false; KTextEditor::Range rx; if(!add) rx=rangeForText(doc, r.castToSimpleRange(), name); if(!add && rx.isValid()) { if(replace.isEmpty()) { eatLeadingWhitespace(doc, rx, r.castToSimpleRange()); doc->removeText(rx); } else doc->replaceText(rx, replace); ret=true; } else { const IndexedString idxLists(lists); KDevelop::DUChainReadLocker lock(KDevelop::DUChain::lock()); KDevelop::ReferencedTopDUContext topctx=DUChain::self()->chainForDocument(idxLists); QList decls; for(int i=0; iusesCount(); i++) { Use u = topctx->uses()[i]; if(!r.contains(u.m_range)) continue; //We just want the uses in the range, not the whole file Declaration* d=u.usedDeclaration(topctx); if(d && d->topContext()->url()==idxLists) decls += d; } if(add && decls.isEmpty()) { doc->insertText(r.castToSimpleRange().start(), ' '+name); ret=true; } else foreach(Declaration* d, decls) { r.start=d->range().end; for(int lineNum = r.start.line; lineNum <= r.end.line; lineNum++) { int endParenIndex = doc->line(lineNum).indexOf(')'); if(endParenIndex >= 0) { r.end = CursorInRevision(lineNum, endParenIndex); break; } } if(!r.isEmpty()) { ret = ret || followUses(doc, r, name, lists, add, replace); } } } return ret; } QString dotlessRelativeUrl(const QUrl &baseUrl, const QUrl& url) { QString dotlessRelative = QUrl::relativeUrl(baseUrl, url); if (dotlessRelative.startsWith(QLatin1String("./"))) dotlessRelative.remove(0, 2); return dotlessRelative; } QString relativeToLists(const QUrl &listsPath, const QUrl& url) { QUrl listsFolder(listsPath.upUrl()); listsFolder.adjustPath(QUrl::AddTrailingSlash); return dotlessRelativeUrl(listsFolder, url); } QUrl afterMoveUrl(const QUrl &origUrl, const QUrl& movedOrigUrl, const QUrl& movedNewUrl) { QString difference = dotlessRelativeUrl(movedOrigUrl, origUrl); return QUrl(movedNewUrl, difference); } QString itemListspath(const ProjectBaseItem* item) { const DescriptorAttatched *desc = 0; if (item->parent()->target()) desc = dynamic_cast(item->parent()); else if (item->type() == ProjectBaseItem::BuildFolder) desc = dynamic_cast(item); if (!desc) return QString(); return desc->descriptor().filePath; } bool itemAffected(const ProjectBaseItem *item, const QUrl &changeUrl) { QUrl listsPath = itemListspath(item); if (listsPath.isEmpty()) return false; QUrl listsFolder(listsPath); listsFolder = listsFolder.upUrl(); //Who thought it was a good idea to have QUrl::isParentOf return true if the urls are equal? return listsFolder.QUrl::isParentOf(changeUrl); } QList cmakeListedItemsAffectedByUrlChange(const IProject *proj, const QUrl &url, QUrl rootUrl) { if (rootUrl.isEmpty()) rootUrl = url; QList dirtyItems; const QList sameUrlItems = proj->itemsForUrl(url); for (ProjectBaseItem* sameUrlItem : sameUrlItems) { if (itemAffected(sameUrlItem, rootUrl)) dirtyItems.append(sameUrlItem); const auto childItems = sameUrlItem->children(); dirtyItems.reserve(dirtyItems.size() + childItems.size()); for (ProjectBaseItem* childItem : childItems) { dirtyItems.append(cmakeListedItemsAffectedByUrlChange(childItem->project(), childItem->url(), rootUrl)); } } return dirtyItems; } QList cmakeListedItemsAffectedByItemsChanged(const QList &items) { QList dirtyItems; dirtyItems.reserve(items.size()); for (ProjectBaseItem *item : items) { dirtyItems.append(cmakeListedItemsAffectedByUrlChange(item->project(), item->url())); } return dirtyItems; } bool changesWidgetRenameFolder(const CMakeFolderItem *folder, const QUrl &newUrl, ApplyChangesWidget *widget) { QString lists = folder->descriptor().filePath; widget->addDocuments(IndexedString(lists)); QString relative(relativeToLists(lists, newUrl)); KTextEditor::Range range = folder->descriptor().argRange().castToSimpleRange(); return widget->document()->replaceText(range, relative); } bool changesWidgetRemoveCMakeFolder(const CMakeFolderItem *folder, ApplyChangesWidget *widget) { widget->addDocuments(IndexedString(folder->descriptor().filePath)); KTextEditor::Range range = folder->descriptor().range().castToSimpleRange(); return widget->document()->removeText(range); } bool changesWidgetAddFolder(const QUrl &folderUrl, const CMakeFolderItem *toFolder, ApplyChangesWidget *widget) { QUrl lists(toFolder->url(), "CMakeLists.txt"); QString relative(relativeToLists(lists, folderUrl)); if (relative.endsWith('/')) relative.chop(1); QString insert = QString("add_subdirectory(%1)").arg(relative); widget->addDocuments(IndexedString(lists)); return widget->document()->insertLine(widget->document()->lines(), insert); } bool changesWidgetMoveTargetFile(const ProjectBaseItem *file, const QUrl &newUrl, ApplyChangesWidget *widget) { const DescriptorAttatched *desc = dynamic_cast(file->parent()); if (!desc || desc->descriptor().arguments.isEmpty()) { return false; } RangeInRevision targetRange(desc->descriptor().arguments.first().range().end, desc->descriptor().argRange().end); QString listsPath = desc->descriptor().filePath; QString newRelative = relativeToLists(listsPath, newUrl); QString oldRelative = relativeToLists(listsPath, file->url()); widget->addDocuments(IndexedString(listsPath)); return followUses(widget->document(), targetRange, oldRelative, listsPath, false, newRelative); } bool changesWidgetAddFileToTarget(const ProjectFileItem *item, const ProjectTargetItem *target, ApplyChangesWidget *widget) { const DescriptorAttatched *desc = dynamic_cast(target); if (!desc || desc->descriptor().arguments.isEmpty()) { return false; } RangeInRevision targetRange(desc->descriptor().arguments.first().range().end, desc->descriptor().range().end); QString lists = desc->descriptor().filePath; QString relative = relativeToLists(lists, item->url()); widget->addDocuments(IndexedString(lists)); return followUses(widget->document(), targetRange, relative, lists, true, QString()); } bool changesWidgetRemoveFileFromTarget(const ProjectBaseItem *item, ApplyChangesWidget *widget) { const DescriptorAttatched *desc = dynamic_cast(item->parent()); if (!desc || desc->descriptor().arguments.isEmpty()) { return false; } RangeInRevision targetRange(desc->descriptor().arguments.first().range().end, desc->descriptor().range().end); QString lists = desc->descriptor().filePath; QString relative = relativeToLists(lists, item->url()); widget->addDocuments(IndexedString(lists)); return followUses(widget->document(), targetRange, relative, lists, false, QString()); } bool changesWidgetRemoveItems(const QSet &items, ApplyChangesWidget *widget) { for (ProjectBaseItem *item : items) { CMakeFolderItem *folder = dynamic_cast(item); if (folder && !changesWidgetRemoveCMakeFolder(folder, widget)) return false; else if (item->parent()->target() && !changesWidgetRemoveFileFromTarget(item, widget)) return false; } return true; } bool changesWidgetRemoveFilesFromTargets(const QList &files, ApplyChangesWidget *widget) { for (ProjectBaseItem* file : files) { Q_ASSERT(file->parent()->target()); if (!changesWidgetRemoveFileFromTarget(file, widget)) return false; } return true; } bool changesWidgetAddFilesToTarget(const QList &files, const ProjectTargetItem* target, ApplyChangesWidget *widget) { for (ProjectFileItem* file : files) { if (!changesWidgetAddFileToTarget(file, target, widget)) return false; } return true; } CMakeFolderItem* nearestCMakeFolder(ProjectBaseItem* item) { while(!dynamic_cast(item) && item) item = item->parent(); return dynamic_cast(item); } } diff --git a/plugins/custommake/makefileresolver/makefileresolver.cpp b/plugins/custommake/makefileresolver/makefileresolver.cpp index 0d083983ad..bebbba7960 100644 --- a/plugins/custommake/makefileresolver/makefileresolver.cpp +++ b/plugins/custommake/makefileresolver/makefileresolver.cpp @@ -1,641 +1,641 @@ /* * KDevelop C++ Language Support * * Copyright 2007 David Nolden * Copyright 2014 Kevin Funk * * 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. */ #include "makefileresolver.h" #include "helper.h" #include #include #include #include #include #include #include #include #include #include #include #include // #define VERBOSE #if 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() { } ModificationRevisionSet modificationTime; Path::List paths; Path::List frameworkDirectories; QHash defines; QString errorMessage, longErrorMessage; bool failed = false; QMap failedFiles; QDateTime failTime; }; typedef QMap Cache; static Cache s_cache; static QMutex s_cacheMutex; } /** * 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) **/ class SourcePathInformation { public: explicit SourcePathInformation(const QString& path) : m_path(path) { } QString createCommand(const QString& absoluteFile, const QString& workingDirectory, const QString& makeParameters) const { QString relativeFile = Path(workingDirectory).relativePath(Path(absoluteFile)); #ifndef Q_OS_FREEBSD // GNU make implicitly enables "-w" for sub-makes, we don't want that QLatin1String noPrintDirFlag = QLatin1String(" --no-print-directory"); #else QLatin1String noPrintDirFlag; #endif return "make -k" + noPrintDirFlag + " -W \'" + absoluteFile + "\' -W \'" + relativeFile + "\' -n " + makeParameters; } bool hasMakefile() const { QFileInfo makeFile(m_path, QStringLiteral("Makefile")); return makeFile.exists(); } QStringList possibleTargets(const QString& targetBaseName) const { const QStringList ret{ ///@todo open the make-file, and read the target-names from there. //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. targetBaseName + QLatin1String(".o"), targetBaseName + QLatin1String(".lo"), //ret << targetBaseName + ".lo " + targetBaseName + ".o"; targetBaseName + QLatin1String(".ko"), }; return ret; } private: QString m_path; }; static void mergePaths(KDevelop::Path::List& destList, const KDevelop::Path::List& srcList) { for (const Path& path : srcList) { if (!destList.contains(path)) destList.append(path); } } void PathResolutionResult::mergeWith(const PathResolutionResult& rhs) { mergePaths(paths, rhs.paths); mergePaths(frameworkDirectories, rhs.frameworkDirectories); includePathDependency += rhs.includePathDependency; defines.unite(rhs.defines); } PathResolutionResult::PathResolutionResult(bool success, const QString& errorMessage, const QString& longErrorMessage) : success(success) , errorMessage(errorMessage) , longErrorMessage(longErrorMessage) {} PathResolutionResult::operator bool() const { return success; } ModificationRevisionSet MakeFileResolver::findIncludePathDependency(const QString& file) { QString oldSourceDir = m_source; QString oldBuildDir = m_build; Path currentWd(mapToBuild(file)); ModificationRevisionSet rev; while (currentWd.hasParent()) { currentWd = currentWd.parent(); QString path = currentWd.toLocalFile(); QFileInfo makeFile(QDir(path), QStringLiteral("Makefile")); if (makeFile.exists()) { IndexedString makeFileStr(makeFile.filePath()); rev.addModificationRevision(makeFileStr, ModificationRevision::revisionForFile(makeFileStr)); break; } } setOutOfSourceBuildSystem(oldSourceDir, oldBuildDir); return rev; } bool MakeFileResolver::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; } MakeFileResolver::MakeFileResolver() { } ///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 MakeFileResolver::resolveIncludePath(const QString& file) { if (file.isEmpty()) { // for unit tests with temporary files return PathResolutionResult(); } QFileInfo fi(file); return resolveIncludePath(fi.fileName(), fi.absolutePath()); } QString MakeFileResolver::mapToBuild(const QString &path) const { QString wd = QDir::cleanPath(path); 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 = QDir::cleanPath(m_build + '/' + wd.midRef(m_source.length())); } } return wd; } void MakeFileResolver::clearCache() { QMutexLocker l(&s_cacheMutex); s_cache.clear(); } PathResolutionResult MakeFileResolver::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 (QFileInfo(workingDirectory).isRelative()) { QUrl u = QUrl::fromLocalFile(QDir::currentPath()); if (workingDirectory == QLatin1String(".")) workingDirectory = QString(); else if (workingDirectory.startsWith(QLatin1String("./"))) workingDirectory.remove(0, 2); if (!workingDirectory.isEmpty()) { u = u.adjusted(QUrl::StripTrailingSlash); u.setPath(u.path() + '/' + workingDirectory); } workingDirectory = u.toLocalFile(); } else workingDirectory = _workingDirectory; ifTest(cout << "working-directory: " << workingDirectory.toLocal8Bit().data() << " file: " << file.toLocal8Bit().data() << std::endl;) QDir sourceDir(workingDirectory); QDir dir = QDir(mapToBuild(sourceDir.absolutePath())); QFileInfo makeFile(dir, QStringLiteral("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()) { const QString checkFor = localName + QLatin1Char('/') + file; PathResolutionResult oneUp = resolveIncludePath(checkFor, sourceDir.path(), maxStepsUp-1); if (oneUp.success) { oneUp.mergeWith(resultOnFail); return oneUp; } } } if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.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); Path::List cachedPaths; //If the call doesn't succeed, use the cached not up-to-date version Path::List cachedFWDirs; QHash cachedDefines; 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; cachedFWDirs = it->frameworkDirectories; cachedDefines = it->defines; if (dependency == it->modificationTime) { if (!it->failed) { //We have a valid cached result PathResolutionResult ret(true); ret.paths = it->paths; ret.frameworkDirectories = it->frameworkDirectories; ret.defines = it->defines; ret.mergeWith(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.frameworkDirectories = it->frameworkDirectories; ret.defines = it->defines; ret.mergeWith(resultOnFail); return ret; } else { //Try getting a correct result again } } } } } ///STEP 1: Prepare paths QString targetName; QFileInfo fi(file); QString absoluteFile = file; if (fi.isRelative()) absoluteFile = workingDirectory + '/' + file; absoluteFile = QDir::cleanPath(absoluteFile); int dot; if ((dot = file.lastIndexOf('.')) == -1) { if (!resultOnFail.errorMessage.isEmpty() || !resultOnFail.paths.isEmpty() || !resultOnFail.frameworkDirectories.isEmpty()) return resultOnFail; else return PathResolutionResult(false, i18n("Filename %1 seems to be malformed", file)); } targetName = file.left(dot); QString wd = dir.path(); if (QFileInfo(wd).isRelative()) { wd = QDir::cleanPath(QDir::currentPath() + '/' + wd); } wd = mapToBuild(wd); SourcePathInformation source(wd); QStringList possibleTargets = source.possibleTargets(targetName); ///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(QLatin1Char(' ')), 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. res.defines = cachedDefines; } // a build command could contain only one or more -iframework or -F specifications. if (res.frameworkDirectories.isEmpty()) { res.frameworkDirectories = cachedFWDirs; } { QMutexLocker l(&s_cacheMutex); if (it == s_cache.end()) it = s_cache.insert(dir.path(), CacheEntry()); CacheEntry& ce(*it); ce.paths = res.paths; ce.frameworkDirectories = res.frameworkDirectories; 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() || !resultOnFail.frameworkDirectories.isEmpty())) return resultOnFail; return res; } static QRegularExpression includeRegularExpression() { static const QRegularExpression expression( "\\s(--include-dir=|-I\\s*|-isystem\\s+|-iframework\\s+|-F\\s*)(" "\\'.*\\'|\\\".*\\\"" //Matches "hello", 'hello', 'hello"hallo"', etc. "|" "((?:\\\\.)?([\\S^\\\\]?))+" //Matches /usr/I\ am\ a\ strange\ path/include ")(?=\\s)" ); Q_ASSERT(expression.isValid()); return expression; } PathResolutionResult MakeFileResolver::resolveIncludePathInternal(const QString& file, const QString& workingDirectory, const QString& makeParameters, const SourcePathInformation& source, int maxDepth) { --maxDepth; if (maxDepth < 0) return PathResolutionResult(false); QString fullOutput; executeCommand(source.createCommand(file, workingDirectory, makeParameters), workingDirectory, fullOutput); { QRegExp newLineRx("\\\\\\n"); fullOutput.remove(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 (!includeRegularExpression().match(fullOutput).hasMatch()) { QRegExp makeRx("\\bmake\\s"); int offset = 0; while ((offset = makeRx.indexIn(firstLine, offset)) != -1) { QString prefix = firstLine.left(offset).trimmed(); if (prefix.endsWith(QLatin1String("&&")) || prefix.endsWith(';') || prefix.isEmpty()) { QString newWorkingDirectory = workingDirectory; ///Extract the new working-directory if (!prefix.isEmpty()) { if (prefix.endsWith(QLatin1String("&&"))) 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(QLatin1String("cd ")); if (cdIndex != -1) { newWorkingDirectory = prefix.right(prefix.length() - 3 - cdIndex).trimmed(); if (QFileInfo(newWorkingDirectory).isRelative()) newWorkingDirectory = workingDirectory + '/' + newWorkingDirectory; newWorkingDirectory = QDir::cleanPath(newWorkingDirectory); } } 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(QLatin1String("&&"))) { ///Looks like valid parameters ///Make the file-name absolute, so it can be referenced from any directory QString absoluteFile = file; if (QFileInfo(absoluteFile).isRelative()) absoluteFile = workingDirectory + '/' + file; Path absolutePath(absoluteFile); ///Try once with absolute, and if that fails with relative path of the file SourcePathInformation newSource(newWorkingDirectory); PathResolutionResult res = resolveIncludePathInternal(absolutePath.toLocalFile(), newWorkingDirectory, makeParams, newSource, maxDepth); if (res) return res; return resolveIncludePathInternal(Path(newWorkingDirectory).relativePath(absolutePath), 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 = processOutput(fullOutput, workingDirectory); if (ret.paths.isEmpty() && ret.frameworkDirectories.isEmpty()) return PathResolutionResult(false, i18n("Could not extract include paths from make output"), i18n("Folder: \"%1\" Command: \"%2\" Output: \"%3\"", workingDirectory, source.createCommand(file, workingDirectory, makeParameters), fullOutput)); return ret; } QRegularExpression MakeFileResolver::defineRegularExpression() { static const QRegularExpression pattern( QStringLiteral("-D([^\\s=]+)(?:=(?:\"(.*?)(? 2)) { //probable a quoted path - if (path.endsWith(path.left(1))) { + if (path.endsWith(path.leftRef(1))) { //Quotation is ok, remove it path = path.mid(1, path.length() - 2); } } if (QDir::isRelativePath(path)) path = workingDirectory + '/' + path; const auto& internedPath = internPath(path); const auto& type = match.captured(1); const auto isFramework = type.startsWith(QLatin1String("-iframework")) || type.startsWith(QLatin1String("-F")); if (isFramework) { ret.frameworkDirectories << internedPath; } else { ret.paths << internedPath; } } } { const auto& defineRx = defineRegularExpression(); auto it = defineRx.globalMatch(fullOutput); while (it.hasNext()) { const auto match = it.next(); QString value; if (match.lastCapturedIndex() > 1) { value = unescape(match.capturedRef(match.lastCapturedIndex())); } ret.defines[internString(match.captured(1))] = internString(value); } } return ret; } void MakeFileResolver::resetOutOfSourceBuild() { m_outOfSource = false; } void MakeFileResolver::setOutOfSourceBuildSystem(const QString& source, const QString& build) { if (source == build) { resetOutOfSourceBuild(); return; } m_outOfSource = true; m_source = QDir::cleanPath(source); m_build = QDir::cleanPath(m_build); } Path MakeFileResolver::internPath(const QString& path) const { Path& ret = m_pathCache[path]; if (ret.isEmpty() != path.isEmpty()) { ret = Path(path); } return ret; } QString MakeFileResolver::internString(const QString& path) const { auto it = m_stringCache.constFind(path); if (it != m_stringCache.constEnd()) { return *it; } m_stringCache.insert(path); return path; } // kate: indent-width 2; tab-width 2; diff --git a/plugins/lldb/lldbcommand.cpp b/plugins/lldb/lldbcommand.cpp index 88b80228d5..d8eaed402a 100644 --- a/plugins/lldb/lldbcommand.cpp +++ b/plugins/lldb/lldbcommand.cpp @@ -1,229 +1,229 @@ /* * LLDB specific version of MI command * Copyright 2016 Aetf * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * 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, see . * */ #include "lldbcommand.h" using namespace KDevMI::LLDB; using namespace KDevMI::MI; LldbCommand::LldbCommand(CommandType type, const QString& arguments, CommandFlags flags) : MICommand(type, arguments, flags) { } LldbCommand::~LldbCommand() { } QString LldbCommand::miCommand() const { if (!overrideCmd.isEmpty()) { return overrideCmd; } QString command; bool isMI = false; // TODO: find alternatives to the following command which are not supported in lldb-mi switch(type()) { case BreakCommands: // empty command break; case BreakInfo: // empty command break; case BreakInsert: // in lldb-mi, '-f' must be the last option switch right before location command = QStringLiteral("break-insert"); isMI = true; break; case BreakList: // empty command break; case BreakWatch: command = QStringLiteral("break set var"); break; case DataListChangedRegisters: command = QStringLiteral("data-list-changed-registers"); break; case DataReadMemory: // not implemented, deprecated command = QStringLiteral("data-read-memory"); break; case DataWriteRegisterVariables: command = QStringLiteral("data-write-register-values"); break; case EnableTimings: command = QStringLiteral("enable-timings"); break; case EnvironmentDirectory: command = QStringLiteral("environment-directory"); break; case EnvironmentPath: command = QStringLiteral("environment-path"); break; case EnvironmentPwd: command = QStringLiteral("environment-pwd"); break; case ExecUntil: // TODO: write test case for this command = QStringLiteral("thread until"); break; case FileExecFile: command = QStringLiteral("file-exec-file");//"exec-file" break; case FileListExecSourceFile: command = QStringLiteral("file-list-exec-source-file"); break; case FileListExecSourceFiles: command = QStringLiteral("file-list-exec-source-files"); break; case FileSymbolFile: command = QStringLiteral("file-symbol-file");//"symbol-file" break; case GdbVersion: command = QStringLiteral("gdb-version");//"show version" break; case InferiorTtyShow: command = QStringLiteral("inferior-tty-show"); break; case SignalHandle: command = QStringLiteral("process handle"); break; case TargetDisconnect: command = QStringLiteral("target-disconnect");//"disconnect" break; case TargetDownload: command = QStringLiteral("target-download"); break; case ThreadListIds: command = QStringLiteral("thread-list-ids"); break; case ThreadSelect: command = QStringLiteral("thread-select"); break; case TraceFind: command = QStringLiteral("trace-find"); break; case TraceStart: command = QStringLiteral("trace-start"); break; case TraceStop: command = QStringLiteral("trace-stop"); break; case VarInfoNumChildren: command = QStringLiteral("var-info-num-children"); break; case VarInfoType: command = QStringLiteral("var-info-type"); break; case VarSetFrozen: command = QStringLiteral("var-set-frozen"); break; case VarShowFormat: command = QStringLiteral("var-show-format"); break; default: return MICommand::miCommand(); } if (isMI) { command.prepend('-'); } return command; } QString LldbCommand::cmdToSend() { switch (type()) { // -gdb-set is only partially implemented case GdbSet: { QString env_name = QStringLiteral("environment "); QString disassembly_flavor = QStringLiteral("disassembly-flavor "); if (command_.startsWith(env_name)) { command_.remove(0, env_name.length()); overrideCmd = QStringLiteral("settings set target.env-vars"); } else if (command_.startsWith(disassembly_flavor)) { command_.remove(0, disassembly_flavor.length()); overrideCmd = QStringLiteral("settings set target.x86-disassembly-flavor"); } break; } // find the position to insert '-f' case BreakInsert: { if (!overrideCmd.isEmpty()) { // already done break; } int p = command_.length() - 1; bool quoted = false; if (command_[p] == '"') { quoted = true; // should always be the case } --p; for (; p >= 0; --p) { // find next '"' or ' ' if (quoted) { if (command_[p] == '"' && (p == 0 || command_[p-1] != '\\')) break; } else { if (command_[p] == ' ') break; } } if (p < 0) p = 0; // this means the command is malformated, we proceed anyway. // move other switches like '-d' '-c' into miCommand part - overrideCmd = miCommand() + QLatin1Char(' ') + command_.left(p); + overrideCmd = miCommand() + QLatin1Char(' ') + command_.leftRef(p); command_ = "-f " + command_.midRef(p, command_.length()); break; } case BreakWatch: if (command_.startsWith(QLatin1String("-r "))) { command_ = "-w read " + command_.midRef(3); } else if (command_.startsWith(QLatin1String("-a "))) { command_ = "-w read_write " + command_.midRef(3); } break; case StackListArguments: // some times when adding the command, the current frame is invalid, // but is valid at sending time if (command_.endsWith(QLatin1String("-1 -1"))) { command_.replace(QLatin1String("-1 -1"), QStringLiteral("%1 %1").arg(frame())); } break; default: break; } return MICommand::cmdToSend(); }