/* KDevelop CMake Support * * Copyright 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 "cmakeimportjob.h" #include "cmakeutils.h" #include #include "cmakecommitchangesjob.h" #include "cmakemanager.h" #include "cmakeprojectdata.h" #include "cmakemodelitems.h" #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; class WaitAllJobs : public KCompositeJob { Q_OBJECT public: friend class CMakeManager; WaitAllJobs(QObject* parent) : KCompositeJob(parent) , m_started(false) {} virtual void slotResult(KJob* job) { KCompositeJob::slotResult(job); reconsider(); } void addJob(KJob* job) { Q_ASSERT(thread() == QThread::currentThread()); // Q_ASSERT(thread() == job->thread()); addSubjob(job); job->start(); } public slots: virtual void start() { m_started = true; QMetaObject::invokeMethod(this, "reconsider", Qt::QueuedConnection); } private slots: void reconsider() { if(subjobs().isEmpty() && m_started) { m_started = false; //don't emit the result twice! emitResult(); } } private: bool m_started; }; CMakeImportJob::CMakeImportJob(ProjectFolderItem* dom, CMakeManager* parent) : KJob(parent) , m_project(dom->project()) , m_dom(dom) , m_data(parent->projectData(dom->project())) , m_manager(parent) , m_futureWatcher(new QFutureWatcher) { connect(m_futureWatcher, SIGNAL(finished()), SLOT(importFinished())); } void CMakeImportJob::start() { QFuture future = QtConcurrent::run(this, &CMakeImportJob::initialize); m_futureWatcher->setFuture(future); } void CMakeImportJob::importFinished() { Q_ASSERT(m_project->thread() == QThread::currentThread()); WaitAllJobs* wjob = new WaitAllJobs(this); connect(wjob, SIGNAL(finished(KJob*)), SLOT(waitFinished(KJob*))); foreach(KJob* job, m_jobs) { wjob->addJob(job); } wjob->start(); } void CMakeImportJob::initialize() { ReferencedTopDUContext ctx; ProjectBaseItem* parent = m_dom->parent(); while (parent && !ctx) { DUChainReadLocker lock; ctx = DUChain::self()->chainForDocument(IndexedString(Path(parent->path(), "CMakeLists.txt").pathOrUrl())); parent = parent->parent(); } if (!ctx) { ctx = initializeProject(dynamic_cast(m_dom)); } importDirectory(m_project, m_dom->path(), ctx); } KDevelop::ReferencedTopDUContext CMakeImportJob::initializeProject(CMakeFolderItem* rootFolder) { Path base(CMake::projectRoot(m_project)); QPair initials = CMakeParserUtils::initialVariables(); m_data.clear(); m_data.modulePath=initials.first["CMAKE_MODULE_PATH"]; m_data.vm=initials.first; m_data.vm.insertGlobal("CMAKE_SOURCE_DIR", QStringList(base.toLocalFile())); m_data.vm.insertGlobal("CMAKE_BINARY_DIR", QStringList(CMake::currentBuildDir(m_project).toLocalFile(KUrl::RemoveTrailingSlash))); const Path cachefile(m_manager->buildDirectory(m_project->projectItem()), "CMakeCache.txt"); m_data.cache = CMakeParserUtils::readCache(cachefile); KDevelop::ReferencedTopDUContext buildstrapContext; { const IndexedString idxpath(Path(base, "buildstrap").pathOrUrl()); DUChainWriteLocker lock; buildstrapContext = DUChain::self()->chainForDocument(idxpath); if(buildstrapContext) { buildstrapContext->clearLocalDeclarations(); buildstrapContext->clearImportedParentContexts(); buildstrapContext->deleteChildContextsRecursively(); }else{ buildstrapContext=new TopDUContext(idxpath, RangeInRevision(0,0, 0,0), new ParsingEnvironmentFile(idxpath)); DUChain::self()->addDocumentChain(buildstrapContext); } Q_ASSERT(buildstrapContext); } ReferencedTopDUContext ref=buildstrapContext; foreach(const QString& script, initials.second) { ref = includeScript(CMakeProjectVisitor::findFile(script, m_data.modulePath, QStringList()), base.toLocalFile(), ref); } //Initialize parent parts of the project that don't belong to the tree (because it's a partial import) if(base.isParentOf(m_project->path())) { QList toimport; toimport += base; QStringList includes; while(!toimport.isEmpty()) { const Path currentDir = toimport.takeFirst(); const Path script(currentDir, "CMakeLists.txt"); QString dir = currentDir.toLocalFile(); ref = includeScript(script.toLocalFile(), dir, ref); Q_ASSERT(ref); includes << m_data.properties[DirectoryProperty][dir]["INCLUDE_DIRECTORIES"]; CMakeParserUtils::addDefinitions(m_data.properties[DirectoryProperty][dir]["COMPILE_DEFINITIONS"], &m_data.definitions); CMakeParserUtils::addDefinitions(m_data.vm["CMAKE_CXX_FLAGS"], &m_data.definitions, true); rootFolder->setDefinitions(m_data.definitions); foreach(const Subdirectory& s, m_data.subdirectories) { const Path candidate(currentDir, s.name); if(candidate.isParentOf(m_project->path())) toimport += candidate; } } rootFolder->setIncludeDirectories(includes); rootFolder->setBuildDir(base.relativePath(m_project->path())); } return ref; } void CMakeImportJob::waitFinished(KJob*) { emitResult(); } KDevelop::ReferencedTopDUContext CMakeImportJob::includeScript(const QString& file, const QString& dir, ReferencedTopDUContext parent) { m_manager->addWatcher(m_project, file); QString profile = CMake::currentEnvironment(m_project); const KDevelop::EnvironmentGroupList env( KGlobal::config() ); return CMakeParserUtils::includeScript( file, parent, &m_data, dir, env.variables(profile)); } CMakeCommitChangesJob* CMakeImportJob::importDirectory(IProject* project, const Path& path, const KDevelop::ReferencedTopDUContext& parentTop) { Q_ASSERT(thread() == m_project->thread()); Path cmakeListsPath(path, "CMakeLists.txt"); CMakeCommitChangesJob* commitJob = new CMakeCommitChangesJob(path, m_manager, project); commitJob->moveToThread(thread()); m_jobs += commitJob; if(QFile::exists(cmakeListsPath.toLocalFile())) { kDebug(9042) << "Adding cmake: " << cmakeListsPath << " to the model"; m_data.vm.pushScope(); ReferencedTopDUContext ctx = includeScript(cmakeListsPath.toLocalFile(), path.toLocalFile(), parentTop); Path::List folderList = commitJob->addProjectData(m_data); foreach(const Path& folder, folderList) { if (!m_manager->filterManager()->isValid(folder, true, project)) { continue; } Path newcmakeListsPath(folder, "CMakeLists.txt"); if(!QFile::exists(newcmakeListsPath.toLocalFile())) { kWarning() << "Unable to open " << newcmakeListsPath.toLocalFile(); continue; } CMakeCommitChangesJob* job = importDirectory(project, folder, ctx); job->setFindParentItem(false); connect(commitJob, SIGNAL(folderCreated(KDevelop::ProjectFolderItem*)), job, SLOT(folderAvailable(KDevelop::ProjectFolderItem*))); } m_data.vm.popScope(); } return commitJob; } IProject* CMakeImportJob::project() const { Q_ASSERT(!m_futureWatcher->isRunning()); return m_project; } CMakeProjectData CMakeImportJob::projectData() const { Q_ASSERT(!m_futureWatcher->isRunning()); return m_data; } #include "moc_cmakeimportjob.cpp" #include "cmakeimportjob.moc"