kde-extraapps/kdevelop/languages/cpp/cpputils.cpp
2015-07-26 14:23:17 +03:00

428 lines
13 KiB
C++

/*
Copyright 2007-2008 David Nolden <david.nolden.kdevelop@art-master.de>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "cpputils.h"
#include "setuphelpers.h"
#include "parser/rpp/preprocessor.h"
#include "includepathcomputer.h"
#include <interfaces/icore.h>
#include <interfaces/iprojectcontroller.h>
#include <interfaces/iproject.h>
#include <language/codegen/coderepresentation.h>
#include <language/duchain/declaration.h>
#include <language/duchain/duchain.h>
#include <language/duchain/duchainlock.h>
#include <language/util/includeitem.h>
#include "interfaces/foregroundlock.h"
#include <project/projectmodel.h>
#include <QDirIterator>
#include <QThread>
#include <QCoreApplication>
template<class T>
static QList<T> makeListUnique(const QList<T>& list)
{
QList<T> ret;
QSet<QString> set;
foreach(const T& item, list)
{
if(!set.contains(item))
{
ret << item;
set.insert(item);
}
}
return ret;
}
QString addDot(QString ext) {
if(ext.contains('.')) //We need this check because of the _impl.h thing
return ext;
else
return "." + ext;
}
using namespace KDevelop;
namespace CppUtils
{
int findEndOfInclude(QString line)
{
QString tmp = line;
tmp = tmp.trimmed();
if(!tmp.startsWith("#"))
return -1;
tmp = tmp.mid(1).trimmed();
if(!tmp.startsWith("include"))
return -1;
return line.indexOf("include") + 7;
}
QString sourceOrHeaderCandidate( const QString &path_, bool fast )
{
// get the path of the currently active document
QFileInfo fi( path_ );
QString path = fi.filePath();
// extract the exension
QString ext = fi.suffix();
if ( ext.isEmpty() )
return QString();
// extract the base path (full path without '.' and extension)
QString base = path.left( path.length() - ext.length() - 1 );
//kDebug( 9007 ) << "base: " << base << ", ext: " << ext << endl;
// just the filename without the extension
QString fileNameWoExt = fi.fileName();
if ( !ext.isEmpty() )
fileNameWoExt.replace( "." + ext, "" );
QStringList possibleExts;
// depending on the current extension assemble a list of
// candidate files to look for
QStringList candidates;
// special case for template classes created by the new class dialog
if ( path.endsWith( "_impl.h" ) )
{
QString headerpath = path;
headerpath.replace( "_impl.h", ".h" );
candidates << headerpath;
fileNameWoExt.replace( "_impl", "" );
possibleExts << "h";
}
// if file is a header file search for implementation file
else if ( headerExtensions().contains( ext ) )
{
foreach(const QString& ext, sourceExtensions())
candidates << ( base + addDot(ext) );
possibleExts = sourceExtensions();
}
// if file is an implementation file, search for header file
else if ( sourceExtensions().contains( ext ) )
{
foreach(const QString& ext, headerExtensions())
candidates << ( base + addDot(ext) );
possibleExts = headerExtensions();
}
// search for files from the assembled candidate lists, return the first
// candidate file that actually exists or QString::null if nothing is found.
QStringList::ConstIterator it;
for ( it = candidates.constBegin(); it != candidates.constEnd(); ++it )
{
// kDebug( 9007 ) << "Trying " << ( *it ) << endl;
if ( QFileInfo( *it ).exists() )
{
// kDebug( 9007 ) << "using: " << *it << endl;
return * it;
}
}
if(fast)
return QString();
//kDebug( 9007 ) << "Now searching in project files." << endl;
// Our last resort: search the project file list for matching files
QFileInfo candidateFileWoExt;
QString candidateFileWoExtString;
const IndexedString file(path);
foreach (KDevelop::IProject *project, ICore::self()->projectController()->projects()) {
if (project->inProject(file)) {
foreach(const IndexedString& otherFile, project->fileSet()) {
candidateFileWoExt.setFile(otherFile.str());
//kDebug( 9007 ) << "candidate file: " << otherFile.str() << endl;
if( !candidateFileWoExt.suffix().isEmpty() )
candidateFileWoExtString = candidateFileWoExt.fileName().replace( "." + candidateFileWoExt.suffix(), "" );
if ( candidateFileWoExtString == fileNameWoExt )
{
if ( possibleExts.contains( candidateFileWoExt.suffix() ) || candidateFileWoExt.suffix().isEmpty() )
{
//kDebug( 9007 ) << "checking if " << url << " exists" << endl;
return otherFile.str();
}
}
}
}
}
return QString();
}
bool isHeader(const KUrl &url) {
QFileInfo fi( url.toLocalFile() );
QString path = fi.filePath();
// extract the exension
QString ext = fi.suffix();
if ( ext.isEmpty() )
return true;
return headerExtensions().contains(ext);
}
const Cpp::ReferenceCountedMacroSet& standardMacros()
{
static Cpp::ReferenceCountedMacroSet macros = CppTools::setupStandardMacros();
return macros;
}
QPair<Path, Path> findInclude(const Path::List& includePaths, const Path& localPath,
const QString& includeName, int includeType,
const Path& skipPath, bool quiet){
QPair<Path, Path> ret;
#ifdef DEBUG
kDebug(9007) << "searching for include-file" << includeName;
if( !skipPath.isEmpty() )
kDebug(9007) << "skipping path" << skipPath;
#endif
if (includeName.startsWith('/')) {
QFileInfo info(includeName);
if (info.exists() && info.isReadable() && info.isFile()) {
//kDebug(9007) << "found include file:" << info.absoluteFilePath();
ret.first = Path(info.canonicalFilePath());
ret.second = Path("/");
return ret;
}
}
if (includeType == rpp::Preprocessor::IncludeLocal && localPath != skipPath) {
Path check(localPath, includeName);
QFileInfo info(check.toLocalFile());
if (info.exists() && info.isReadable() && info.isFile()) {
//kDebug(9007) << "found include file:" << info.absoluteFilePath();
ret.first = check;
ret.second = localPath;
return ret;
}
}
//When a path is skipped, we will start searching exactly after that path
bool needSkip = skipPath.isValid();
restart:
foreach( const Path& path, includePaths ) {
if( needSkip ) {
if( path == skipPath ) {
needSkip = false;
continue;
}
}
Path check(path, includeName);
QFileInfo info(check.toLocalFile());
if (info.exists() && info.isReadable() && info.isFile()) {
//kDebug(9007) << "found include file:" << info.absoluteFilePath();
ret.first = check;
ret.second = path;
return ret;
}
}
if( needSkip ) {
//The path to be skipped was not found, so simply start from the begin, considering any path.
needSkip = false;
goto restart;
}
const int idx = includeName.indexOf('/');
if ( idx != -1 ) {
// HACK: parse Qt4 includes and similar even without the full include paths from the project manager
// there, a file in /usr/include/qt4/QtCore/ tries to include sibling files via QtCore/file
ret = findInclude(includePaths, localPath, includeName.mid(idx + 1), rpp::Preprocessor::IncludeLocal, skipPath, false);
}
if( !ret.first.isValid())
{
//Check if there is an available artificial representation
if(!includeName.isNull() && artificialCodeRepresentationExists(IndexedString(includeName)))
{
ret.first = Path(CodeRepresentation::artificialPath(includeName));
kDebug() << "Utilizing Artificial code for include: " << includeName;
}
else if(!quiet ) {
kDebug() << "FAILED to find include-file" << includeName << "in paths:" << includePaths;
}
}
return ret;
}
bool needsUpdate(const Cpp::EnvironmentFilePointer& file, const Path& localPath, const Path::List& includePaths)
{
if(file->needsUpdate())
return true;
///@todo somehow this check should also go into EnvironmentManager
for( Cpp::ReferenceCountedStringSet::Iterator it = file->missingIncludeFiles().iterator(); it; ++it ) {
///@todo store IncludeLocal/IncludeGlobal somewhere
auto included = findInclude( includePaths, localPath, (*it).str(), rpp::Preprocessor::IncludeLocal, Path(), true );
if(included.first.isValid())
return true;
}
return false;
}
/**
* This is an ugly HACK but well... it works and doesn't require an extremely big API refactoring.
*
* Thing is, the findIncludePaths was potentially called from background threads, mostly during
* code completion. From there, it is not safe to call IncludePathComputer::computeForeground.
* To fix this, we now add another eventloop roundtrip, execute that function in the foreground
* and then afterwards compute the rest in the background. This does mean that the background thread
* is _blocked_ while we compute stuff in the foreground.
*
* This must be properly fixed when we port the MissingInclude feature to clang. There, the
* include paths must be queried in the foreground thread, before invoking code completion.
*/
class IncludePathForegroundComputer : public DoInForeground
{
public:
IncludePathForegroundComputer(IncludePathComputer* includePathComputer)
: m_includePathComputer(includePathComputer)
{}
private:
void doInternal() override final
{
m_includePathComputer->computeForeground();
}
IncludePathComputer* m_includePathComputer;
};
Path::List findIncludePaths(const QString& source)
{
Q_ASSERT(QThread::currentThread() == qApp->thread() ||
(!DUChain::lock()->currentThreadHasReadLock() && !DUChain::lock()->currentThreadHasWriteLock()));
IncludePathComputer comp(source);
IncludePathForegroundComputer foreground(&comp);
foreground.doIt();
comp.computeBackground();
return comp.result();
}
QList<KDevelop::IncludeItem> allFilesInIncludePath(const QString& source, bool local, const QString& addPath,
const QStringList& addIncludePaths, bool onlyAddedIncludePaths,
bool prependAddedPathToName, bool allowSourceFiles)
{
QList<KDevelop::IncludeItem> ret;
QStringList paths;
if ( addPath.startsWith('/') ) {
paths << QString("/");
} else {
paths = addIncludePaths;
if(!onlyAddedIncludePaths) {
foreach(const Path& path, findIncludePaths(source)) {
paths += path.toLocalFile();
}
if(local) {
KUrl localPath(source);
localPath.setFileName(QString());
paths.push_front(localPath.toLocalFile());
}
}
}
paths = makeListUnique<QString>(paths);
int pathNumber = 0;
QSet<QString> hadIncludePaths;
foreach(const QString& path, paths)
{
if(hadIncludePaths.contains(path)) {
continue;
}
hadIncludePaths.insert(path);
QString searchPath = path;
if (!addPath.isEmpty() && !addPath.startsWith('/')) {
if (!searchPath.endsWith('/')) {
searchPath += '/';
}
searchPath += addPath;
}
QDirIterator dirContent(searchPath);
while(dirContent.hasNext()) {
dirContent.next();
KDevelop::IncludeItem item;
item.name = dirContent.fileName();
if(item.name.startsWith('.') || item.name.endsWith("~")) //This filters out ".", "..", and hidden files, and backups
continue;
QString suffix = dirContent.fileInfo().suffix();
if(!dirContent.fileInfo().suffix().isEmpty() && !headerExtensions().contains(suffix) && (!allowSourceFiles || !sourceExtensions().contains(suffix)))
continue;
QString fullPath = dirContent.fileInfo().canonicalFilePath();
if (hadIncludePaths.contains(fullPath)) {
continue;
} else {
hadIncludePaths.insert(fullPath);
}
if(prependAddedPathToName) {
item.name = addPath + item.name;
item.basePath = path;
} else {
item.basePath = searchPath;
}
item.isDirectory = dirContent.fileInfo().isDir();
item.pathNumber = pathNumber;
ret << item;
}
++pathNumber;
}
return ret;
}
QStringList headerExtensions()
{
static const QStringList headerExtensions = QString("h,H,hh,hxx,hpp,tlh,h++").split(',');
return headerExtensions;
}
QStringList sourceExtensions()
{
static const QStringList sourceExtensions = QString("c,cc,cpp,c++,cxx,C,m,mm,M,inl,_impl.h").split(',');
return sourceExtensions;
}
}