Commit 9d86e6f4 authored by Aleix Pol Gonzalez's avatar Aleix Pol Gonzalez 🐧
Browse files

Merge branch 'cmakesplit'

With this change, I'm making it so the project will be parsed from
a separate thread and will pass the data to another thread that will
do the tree creation.

BUG: 293191
parents be287fdd 580b4428
project(cmakemanager)
kde4_no_enable_final(cmakemanager)
include_directories(
${CMAKE_CURRENT_SOURCE_DIR}/parser
${cmakebuilder_SOURCE_DIR}
)
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/parser)
add_subdirectory(tests)
add_subdirectory(icons)
......@@ -47,6 +44,9 @@ set( cmakemanager_SRCS
cmakenavigationwidget.cpp
cmakemanager.cpp
cmakecodecompletionmodel.cpp
cmakecommitchangesjob.cpp
cmakeimportjob.cpp
cmakeedit.cpp
)
set( cmakemanager_UI
......
/* KDevelop CMake Support
*
* Copyright 2013 Aleix Pol Gonzalez <aleixpol@kde.org>
*
* 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 "cmakecommitchangesjob.h"
#include "cmakeprojectdata.h"
#include "testing/ctestutils.h"
#include "cmakemodelitems.h"
#include "cmakeutils.h"
#include "cmakemanager.h"
#include <cmakeparserutils.h>
#include <project/projectfiltermanager.h>
#include <project/interfaces/iprojectfilter.h>
#include <QThread>
using namespace KDevelop;
static ProjectFileItem* containsFile(const KUrl& file, const QList<ProjectFileItem*>& tfiles)
{
foreach(ProjectFileItem* f, tfiles) {
if(f->url()==file)
return f;
}
return 0;
}
static QStringList resolvePaths(const KUrl& baseUrl, const QStringList& pathsToResolve)
{
QStringList resolvedPaths;
foreach(const QString& pathToResolve, pathsToResolve)
{
QString dir(pathToResolve);
if(!pathToResolve.startsWith("#[") && !pathToResolve.startsWith("$<"))
{
if(KUrl( pathToResolve ).isRelative())
{
KUrl path(baseUrl);
path.addPath(pathToResolve);
dir=path.toLocalFile();
}
KUrl simp(dir); //We use this to simplify dir
simp.cleanPath();
dir=simp.toLocalFile();
}
resolvedPaths.append(dir);
}
return resolvedPaths;
}
static QSet<QString> filterFiles(const QFileInfoList& orig, const KUrl& base, IProject* project, ProjectFilterManager* filter)
{
QSet<QString> ret;
ret.reserve(orig.size());
foreach(const QFileInfo& info, orig)
{
const QString str = info.fileName();
KUrl url = base;
url.addPath(str);
if (!filter->isValid(url, info.isDir(), project)) {
continue;
}
ret.insert(str);
}
return ret;
}
static bool isCorrectFolder(const KUrl& url, IProject* p)
{
KUrl cache(url,"CMakeCache.txt");
bool ret = !QFile::exists(cache.toLocalFile());
ret &= !CMake::allBuildDirs(p).contains(url.toLocalFile(KUrl::RemoveTrailingSlash));
return ret;
}
template <class T>
static bool textInList(const QVector<T>& list, KDevelop::ProjectBaseItem* item)
{
foreach(const T& s, list) {
if(item->text()==s.name)
return true;
}
return false;
}
static QList<KDevelop::ProjectBaseItem*> cleanupBuildFolders(CMakeFolderItem* item, const QVector<Subdirectory>& subs)
{
QList<ProjectBaseItem*> ret;
QList<KDevelop::ProjectFolderItem*> folders = item->folderList();
foreach(KDevelop::ProjectFolderItem* folder, folders) {
CMakeFolderItem* cmfolder = dynamic_cast<CMakeFolderItem*>(folder);
if(cmfolder && cmfolder->formerParent()==item && !textInList<Subdirectory>(subs, folder))
ret += folder;
}
return ret;
}
/////////////////////////////////////////
CMakeCommitChangesJob::CMakeCommitChangesJob(const KUrl& url, CMakeManager* manager, KDevelop::IProject* project)
: KJob()
, m_url(url)
, m_project(project)
, m_manager(manager)
, m_projectDataAdded(false)
, m_parentItem(0)
, m_waiting(false)
, m_findParent(true)
{
setObjectName(url.prettyUrl());
}
KUrl::List CMakeCommitChangesJob::addProjectData(const CMakeProjectData& data)
{
m_projectDataAdded = true;
KUrl::List ret;
m_tests = data.testSuites;
QSet<QString> alreadyAdded;
foreach(const Subdirectory& subf, data.subdirectories) {
if(subf.name.isEmpty() || alreadyAdded.contains(subf.name)) //empty case would not be necessary if we didn't process the wrong lines
continue;
alreadyAdded.insert(subf.name);
m_subdirectories += subf;
KUrl path(subf.name);
if(path.isRelative())
{
path=m_url;
path.addPath(subf.name);
}
path.adjustPath(KUrl::AddTrailingSlash);
ret += path;
}
QString dir = m_url.toLocalFile(KUrl::RemoveTrailingSlash);
if(data.vm.value("CMAKE_INCLUDE_CURRENT_DIR")==QStringList("ON")) {
m_directories += dir;
m_directories += CMakeParserUtils::binaryPath(dir, m_project->folder().toLocalFile(KUrl::RemoveTrailingSlash), CMake::currentBuildDir(m_project).toLocalFile(KUrl::RemoveTrailingSlash));
}
m_directories += resolvePaths(m_url, data.properties[DirectoryProperty][dir]["INCLUDE_DIRECTORIES"]);
m_directories.removeAll(QString());
m_definitions = data.properties[DirectoryProperty][dir]["COMPILE_DEFINITIONS"];
foreach(const Target& t, data.targets) {
const QMap<QString, QStringList>& targetProps = data.properties[TargetProperty][t.name];
if(targetProps["FOLDER"]==QStringList("CTestDashboardTargets"))
continue; //filter some annoying targets
ProcessedTarget target;
target.target = t;
target.defines = targetProps["COMPILE_DEFINITIONS"];
target.includes = targetProps["INCLUDE_DIRECTORIES"];
target.outputName = targetProps.value("OUTPUT_NAME", QStringList(t.name)).join(QString());
target.location = targetProps["LOCATION"].join(QString());
foreach(const QString& dep, t.libraries) {
const QMap<QString, QStringList>& depData = data.properties.value(TargetProperty).value(dep);
if(!depData.isEmpty()) {
target.includes += depData["INTERFACE_INCLUDE_DIRECTORIES"];
target.defines += depData["INTERFACE_COMPILE_DEFINITIONS"];
} else {
kDebug() << "error: couldn't find dependency " << dep << data.properties.value(TargetProperty).keys();
}
}
m_targets += target;
}
return ret;
}
void CMakeCommitChangesJob::start()
{
Q_ASSERT(m_project->thread() == QThread::currentThread());
if(!m_parentItem && m_findParent) {
if(m_url == m_project->folder()) {
m_parentItem = m_project->projectItem()->folder();
} else {
QList<ProjectFolderItem*> folders = m_project->foldersForUrl(m_url);
if(!folders.isEmpty())
m_parentItem = folders.first();
}
}
if((!m_projectDataAdded && m_parentItem) || dynamic_cast<CMakeFolderItem*>(m_parentItem)) {
QMetaObject::invokeMethod(this, "makeChanges", Qt::QueuedConnection);
m_waiting = false;
} else
m_waiting = true;
}
void CMakeCommitChangesJob::makeChanges()
{
Q_ASSERT(m_project->thread() == QThread::currentThread());
ProjectFolderItem* f = m_parentItem;
m_manager->addWatcher(m_project, m_url.toLocalFile(KUrl::AddTrailingSlash));
if(!m_projectDataAdded) {
reloadFiles();
return;
}
CMakeFolderItem* folder = dynamic_cast<CMakeFolderItem*>(f);
Q_ASSERT(folder);
qDeleteAll(cleanupBuildFolders(folder, m_subdirectories));
foreach(const Subdirectory& subf, m_subdirectories)
{
KUrl path(subf.name);
if(path.isRelative())
{
path=m_url;
path.addPath(subf.name);
}
path.adjustPath(KUrl::AddTrailingSlash);
if (!m_manager->filterManager()->isValid(path, true, m_project)) {
continue;
}
if(QDir(path.toLocalFile()).exists())
{
CMakeFolderItem* parent=folder;
if(path.upUrl()!=m_url)
parent=0;
CMakeFolderItem* a = 0;
ProjectFolderItem* ff = folder->folderNamed(subf.name);
if(ff)
{
if(ff->type()!=ProjectBaseItem::BuildFolder)
delete ff;
else
a = static_cast<CMakeFolderItem*>(ff);
}
if(!a)
a = new CMakeFolderItem( folder->project(), path, subf.build_dir, parent );
else
a->setUrl(path);
emit folderCreated(a);
if(!parent) {
a->setFormerParent(folder);
m_manager->addPending(path, a);
}
a->setDescriptor(subf.desc);
}
}
folder->setIncludeDirectories(m_directories);
folder->defineVariables(m_definitions);
QSet<ProjectTargetItem*> deletableTargets = folder->targetList().toSet();
foreach ( const ProcessedTarget& pt, m_targets)
{
const Target& t = pt.target;
KUrl resolvedPath;
if(!pt.location.isEmpty())
resolvedPath=CMake::resolveSystemDirs(folder->project(), QStringList(pt.location)).first();
KDevelop::ProjectTargetItem* targetItem = folder->targetNamed(t.type, t.name);
if (targetItem)
deletableTargets.remove(targetItem);
else {
switch(t.type)
{
case Target::Library:
targetItem = new CMakeLibraryTargetItem( m_project, t.name, folder, pt.outputName, resolvedPath);
break;
case Target::Executable:
targetItem = new CMakeExecutableTargetItem( m_project, t.name, folder, pt.outputName, resolvedPath);
break;
case Target::Custom:
targetItem = new CMakeCustomTargetItem( m_project, t.name, folder, pt.outputName );
break;
}
}
DUChainAttatched* duchainAtt=dynamic_cast<DUChainAttatched*>(targetItem);
if(duchainAtt) {
duchainAtt->setDeclaration(t.declaration);
}
DescriptorAttatched* descAtt=dynamic_cast<DescriptorAttatched*>(targetItem);
if(descAtt)
descAtt->setDescriptor(t.desc);
CompilationDataAttached* incAtt = dynamic_cast<CompilationDataAttached*>(targetItem);
if(incAtt) {
incAtt->setIncludeDirectories(resolvePaths(m_url, pt.includes));
incAtt->defineVariables(pt.defines);
}
KUrl::List tfiles;
foreach( const QString & sFile, t.files)
{
if(sFile.startsWith("#[") || sFile.isEmpty() || sFile.endsWith('/'))
continue;
KUrl sourceFile(sFile);
// important: we want the behavior of KUrl::isRelative(), *not* KUrl::isRelativeUrl()
if(sourceFile.isRelative()) {
sourceFile = m_url;
sourceFile.addPath( sFile );
if(!QFile::exists(sourceFile.toLocalFile())) {
sourceFile.clear();
}
}
sourceFile.cleanPath();
if(!sourceFile.isEmpty())
tfiles += sourceFile;
kDebug(9042) << "..........Adding:" << sourceFile << sFile << m_url;
}
setTargetFiles(targetItem, tfiles);
}
qDeleteAll(deletableTargets);
reloadFiles();
CTestUtils::createTestSuites(m_tests, folder);
}
void CMakeCommitChangesJob::setTargetFiles(ProjectTargetItem* target, const KUrl::List& files)
{
QList<ProjectFileItem*> tfiles = target->fileList();
foreach(ProjectFileItem* file, tfiles) {
if(!files.contains(file->url()))
delete file;
}
tfiles = target->fileList(); //We need to recreate the list without the removed items
foreach(const KUrl& file, files) {
ProjectFileItem* f = containsFile(file, tfiles);
if(!f)
new KDevelop::ProjectFileItem( target->project(), file, target );
}
}
void CMakeCommitChangesJob::reloadFiles(ProjectFolderItem* item)
{
QDir d(item->url().toLocalFile());
if(!d.exists()) {
kDebug() << "Trying to return a directory that doesn't exist:" << item->url();
return;
}
KUrl folderurl = item->url();
folderurl.cleanPath();
const QFileInfoList entriesL = d.entryInfoList(QDir::AllEntries | QDir::NoDotAndDotDot);
QSet<QString> entries = filterFiles(entriesL, folderurl, item->project(), m_manager->filterManager());
kDebug() << "Reloading Directory!" << folderurl;
//We look for removed elements
foreach(ProjectBaseItem* it, item->children())
{
if(it->type()==ProjectBaseItem::Target || it->type()==ProjectBaseItem::ExecutableTarget || it->type()==ProjectBaseItem::LibraryTarget)
continue;
QString current=it->text();
KUrl fileurl = folderurl;
fileurl.addPath(current);
if(!entries.contains(current))
delete it;
else if(!it->url().equals(fileurl, KUrl::CompareWithoutTrailingSlash))
it->setUrl(fileurl);
}
//We look for new elements
QList<ProjectBaseItem*> newItems;
foreach( const QString& entry, entries )
{
KUrl fileurl = folderurl;
fileurl.addPath( entry );
if(item->hasFileOrFolder( entry ))
continue;
if( QFileInfo( fileurl.toLocalFile() ).isDir() )
{
fileurl.adjustPath(KUrl::AddTrailingSlash);
ProjectFolderItem* pendingfolder = m_manager->takePending(fileurl);
if(pendingfolder) {
newItems += pendingfolder;
} else if(isCorrectFolder(fileurl, item->project())) {
fileurl.adjustPath(KUrl::AddTrailingSlash);
ProjectFolderItem* it = new ProjectFolderItem( item->project(), fileurl, 0 );
reloadFiles(it);
m_manager->addWatcher(item->project(), fileurl.toLocalFile());
newItems += it;
}
}
else
{
newItems += new KDevelop::ProjectFileItem( item->project(), fileurl, 0 );
}
}
foreach(ProjectBaseItem* it, newItems)
item->appendRow(it);
}
void CMakeCommitChangesJob::folderAvailable(ProjectFolderItem* item)
{
if(item->url() == m_url) {
m_parentItem = item;
if(m_waiting) {
start();
Q_ASSERT(!m_waiting);
}
}
}
void CMakeCommitChangesJob::reloadFiles()
{
Q_ASSERT(m_project->thread() == QThread::currentThread());
Q_ASSERT(m_parentItem);
reloadFiles(m_parentItem);
emitResult();
}
void CMakeCommitChangesJob::setFindParentItem(bool find)
{
m_findParent = find;
}
/* KDevelop CMake Support
*
* Copyright 2013 Aleix Pol Gonzalez <aleixpol@kde.org>
*
* 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.
*/
#ifndef CMAKECOMMITCHANGESJOB_H
#define CMAKECOMMITCHANGESJOB_H
#include <KJob>
#include <KUrl>
#include <cmaketypes.h>
namespace KDevelop {
class IProject;
class ProjectTargetItem;
class ProjectFolderItem;
}
class CMakeManager;
class CMakeFolderItem;
class CMakeProjectData;
struct ProcessedTarget
{
Target target;
QStringList includes;
QStringList defines;
QString outputName;
QString location;
};
Q_DECLARE_TYPEINFO(ProcessedTarget, Q_MOVABLE_TYPE);
class CMakeCommitChangesJob : public KJob
{
Q_OBJECT
public:
explicit CMakeCommitChangesJob(const KUrl& url, CMakeManager* manager, KDevelop::IProject* project);
KUrl::List addProjectData(const CMakeProjectData& data);
void setFindParentItem(bool find);
virtual void start();
public slots:
void reloadFiles();
void folderAvailable(KDevelop::ProjectFolderItem* item);
signals:
void folderCreated(KDevelop::ProjectFolderItem* item);
private slots:
void makeChanges();
private:
void reloadFiles(KDevelop::ProjectFolderItem* item);
void setTargetFiles(KDevelop::ProjectTargetItem* target, const KUrl::List& files);
KUrl m_url;
QVector<Subdirectory> m_subdirectories;
QVector<ProcessedTarget> m_targets;
QVector<Test> m_tests;
KDevelop::IProject* m_project;
CMakeManager* m_manager;
QStringList m_directories;
QStringList m_definitions;
bool m_projectDataAdded;
KDevelop::ProjectFolderItem* m_parentItem;
bool m_waiting;
bool m_reloadFiles;
bool m_findParent;
};
#endif // CMAKECOMMITCHANGESJOB_H
/* KDevelop CMake Support
*
* Copyright 2007-2013 Aleix Pol <aleixpol@kde.org>
*
* 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 <KTextEditor/Document>
#include <language/duchain/duchainlock.h>
#include <language/duchain/topducontext.h>
#include <language/duchain/duchain.h>
#include <language/duchain/use.h>
#include <language/duchain/declaration.h>
#include <language/codegen/applychangeswidget.h>
#include <project/projectmodel.h>
#include "cmakemodelitems.h"
using namespace KDevelop;