diff --git a/kdevplatform/project/interfaces/ibuildsystemmanager.h b/kdevplatform/project/interfaces/ibuildsystemmanager.h index ea06d0f678..85ebeb9713 100644 --- a/kdevplatform/project/interfaces/ibuildsystemmanager.h +++ b/kdevplatform/project/interfaces/ibuildsystemmanager.h @@ -1,143 +1,148 @@ /* This file is part of KDevelop Copyright 2006 Matt Rogers Copyright 2006 Hamish Rodda Copyright 2007 Andreas Pakulat This library 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 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. */ #ifndef KDEVPLATFORM_IBUILDSYSTEMMANAGER_H #define KDEVPLATFORM_IBUILDSYSTEMMANAGER_H #include "iprojectfilemanager.h" #include namespace KDevelop { class IProjectBuilder; class ProjectTargetItem; /** * Manages the build system of the project. * * Use/Implement the IProjectFileManager interface to manage files. * * @author Matt Rogers , Hamish Rodda */ class KDEVPLATFORMPROJECT_EXPORT IBuildSystemManager : public virtual IProjectFileManager { public: ~IBuildSystemManager() override; enum BuildFeature { Includes /**< This project supports passing include directives to the compiler */, Defines /**< This project supports passing preprocessor defines to compiler */ }; Q_DECLARE_FLAGS( BuildFeatures, BuildFeature ) /** * Provide access to the builder. This method never returns * null, if it does that is a bug in the plugin. A BuildSystemManager * always has a project builder associated with it. */ virtual IProjectBuilder* builder() const = 0; /** * Provide a list of include directories. */ virtual Path::List includeDirectories(ProjectBaseItem*) const = 0; /** * Provide a list of framework directories. */ virtual Path::List frameworkDirectories(ProjectBaseItem*) const = 0; /** * Provide a list of preprocessor defines for the project item */ virtual QHash defines(ProjectBaseItem*) const = 0; /** * Create a new target * * Creates the target specified by @p target to the folder @p parent and * modifies the underlying build system if needed */ virtual ProjectTargetItem* createTarget(const QString& target, ProjectFolderItem *parent) = 0; /** * Remove a target * * Removes the target specified by @p target and * modifies the underlying build system if needed. */ virtual bool removeTarget(ProjectTargetItem *target) = 0; /** * Get a list of all the targets in this project * * The list returned by this function should be checked to verify it is not * empty before using it * * @return The list of targets for this project * @todo implement */ virtual QList targets(ProjectFolderItem*) const = 0; /** * Add a file to a target * * Adds the file specified by @p file to the target @p parent and modifies * the underlying build system if needed. */ virtual bool addFilesToTarget(const QList &files, ProjectTargetItem *target) = 0; /** * Remove files from targets * * Removes the files from the targets they are paired with (@p targetFiles) * Files are not removed from the folders or the filesystem. */ virtual bool removeFilesFromTargets(const QList &files) = 0; /** * Returns if the build system has information specific to @p item */ virtual bool hasBuildInfo(ProjectBaseItem* item) const = 0; /** * Get the toplevel build directory for the project */ virtual Path buildDirectory(ProjectBaseItem*) const = 0; /** * @returns the extra arguments that will be passed to the compiler when building @p item */ virtual QString extraArguments(ProjectBaseItem* item) const = 0; + + /** + * @returns the absolute path to the tool that will be used or an empty path if unknown + */ + virtual Path compiler(KDevelop::ProjectTargetItem* p) const = 0; }; } Q_DECLARE_OPERATORS_FOR_FLAGS( KDevelop::IBuildSystemManager::BuildFeatures ) Q_DECLARE_INTERFACE( KDevelop::IBuildSystemManager, "org.kdevelop.IBuildSystemManager" ) #endif diff --git a/plugins/cmake/cmakemanager.cpp b/plugins/cmake/cmakemanager.cpp index 47c86c6558..9e3ee49882 100644 --- a/plugins/cmake/cmakemanager.cpp +++ b/plugins/cmake/cmakemanager.cpp @@ -1,1028 +1,1061 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * 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 "cmakemanager.h" #include "cmakeedit.h" #include "cmakeutils.h" #include "cmakeprojectdata.h" #include "duchain/cmakeparsejob.h" #include "cmakeimportjsonjob.h" #include "debug.h" #include "settings/cmakepreferences.h" #include "cmakecodecompletionmodel.h" #include "cmakenavigationwidget.h" #include "icmakedocumentation.h" #include "cmakemodelitems.h" #include "testing/ctestutils.h" #include "cmakeserverimportjob.h" #include "cmakeserver.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(KDevelop::IProject*) using namespace KDevelop; K_PLUGIN_FACTORY_WITH_JSON(CMakeSupportFactory, "kdevcmakemanager.json", registerPlugin(); ) const QString DIALOG_CAPTION = i18n("KDevelop - CMake Support"); CMakeManager::CMakeManager( QObject* parent, const QVariantList& ) : KDevelop::AbstractFileManagerPlugin( QStringLiteral("kdevcmakemanager"), parent ) , m_filter( new ProjectFilterManager( this ) ) { if (CMake::findExecutable().isEmpty()) { setErrorDescription(i18n("Unable to find a CMake executable. Is one installed on the system?")); m_highlight = nullptr; return; } m_highlight = new KDevelop::CodeHighlighting(this); new CodeCompletion(this, new CMakeCodeCompletionModel(this), name()); connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &CMakeManager::projectClosing); connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, &CMakeManager::reloadProjects); connect(this, &KDevelop::AbstractFileManagerPlugin::folderAdded, this, &CMakeManager::folderAdded); // m_fileSystemChangeTimer = new QTimer(this); // m_fileSystemChangeTimer->setSingleShot(true); // m_fileSystemChangeTimer->setInterval(100); // connect(m_fileSystemChangeTimer,SIGNAL(timeout()),SLOT(filesystemBuffererTimeout())); } CMakeManager::~CMakeManager() { parseLock()->lockForWrite(); // By locking the parse-mutexes, we make sure that parse jobs get a chance to finish in a good state parseLock()->unlock(); } bool CMakeManager::hasBuildInfo(ProjectBaseItem* item) const { return m_projects[item->project()].compilationData.files.contains(item->path()); } Path CMakeManager::buildDirectory(KDevelop::ProjectBaseItem *item) const { // CMakeFolderItem *fi=dynamic_cast(item); // Path ret; // ProjectBaseItem* parent = fi ? fi->formerParent() : item->parent(); // if (parent) // ret=buildDirectory(parent); // else // ret=Path(CMake::currentBuildDir(item->project())); // // if(fi) // ret.addPath(fi->buildDir()); // return ret; return Path(CMake::currentBuildDir(item->project())); } KDevelop::ProjectFolderItem* CMakeManager::import( KDevelop::IProject *project ) { CMake::checkForNeedingConfigure(project); return AbstractFileManagerPlugin::import(project); } class ChooseCMakeInterfaceJob : public ExecuteCompositeJob { Q_OBJECT public: ChooseCMakeInterfaceJob(IProject* project, CMakeManager* manager) : ExecuteCompositeJob(manager, {}) , project(project) , manager(manager) { } void start() override { server.reset(new CMakeServer(project)); connect(server.data(), &CMakeServer::connected, this, &ChooseCMakeInterfaceJob::successfulConnection); connect(server.data(), &CMakeServer::finished, this, &ChooseCMakeInterfaceJob::failedConnection); } private: void successfulConnection() { auto job = new CMakeServerImportJob(project, server, this); connect(job, &CMakeServerImportJob::result, this, [this, job](){ if (job->error() == 0) { manager->integrateData(job->projectData(), job->project()); } }); addSubjob(job); ExecuteCompositeJob::start(); } void failedConnection(int code) { Q_ASSERT(code > 0); Q_ASSERT(!server->isServerAvailable()); server.clear(); // parse the JSON file auto* job = new CMakeImportJsonJob(project, this); // create the JSON file if it doesn't exist auto commandsFile = CMake::commandsFile(project); if (!QFileInfo::exists(commandsFile.toLocalFile())) { qCDebug(CMAKE) << "couldn't find commands file:" << commandsFile << "- now trying to reconfigure"; addSubjob(manager->builder()->configure(project)); } connect(job, &CMakeImportJsonJob::result, this, [this, job]() { if (job->error() == 0) { manager->integrateData(job->projectData(), job->project()); } }); addSubjob(job); ExecuteCompositeJob::start(); } QSharedPointer server; IProject* const project; CMakeManager* const manager; }; KJob* CMakeManager::createImportJob(ProjectFolderItem* item) { auto project = item->project(); auto job = new ChooseCMakeInterfaceJob(project, this); connect(job, &KJob::result, this, [this, job, project](){ if (job->error() != 0) { qCWarning(CMAKE) << "couldn't load project successfully" << project->name(); m_projects.remove(project); } }); const QList jobs = { job, KDevelop::AbstractFileManagerPlugin::createImportJob(item) // generate the file system listing }; Q_ASSERT(!jobs.contains(nullptr)); auto* composite = new ExecuteCompositeJob(this, jobs); // even if the cmake call failed, we want to load the project so that the project can be worked on composite->setAbortOnError(false); return composite; } // QList CMakeManager::parse(ProjectFolderItem*) // { return QList< ProjectFolderItem* >(); } // // QList CMakeManager::targets() const { QList ret; foreach(IProject* p, m_projects.keys()) { ret+=p->projectItem()->targetList(); } return ret; } CMakeFile CMakeManager::fileInformation(KDevelop::ProjectBaseItem* item) const { const auto & data = m_projects[item->project()].compilationData; QHash::const_iterator it = data.files.constFind(item->path()); if (it == data.files.constEnd()) { // if the item path contains a symlink, then we will not find it in the lookup table // as that only only stores canonicalized paths. Thus, we fallback to // to the canonicalized path and see if that brings up any matches const auto canonicalized = Path(QFileInfo(item->path().toLocalFile()).canonicalFilePath()); it = data.files.constFind(canonicalized); } if (it != data.files.constEnd()) { return *it; } else { // otherwise look for siblings and use the include paths of any we find const Path folder = item->folder() ? item->path() : item->path().parent(); for( it = data.files.constBegin(); it != data.files.constEnd(); ++it) { if (folder.isDirectParentOf(it.key())) { return *it; } } } // last-resort fallback: bubble up the parent chain, and keep looking for include paths if (auto parent = item->parent()) { return fileInformation(parent); } return {}; } Path::List CMakeManager::includeDirectories(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).includes; } Path::List CMakeManager::frameworkDirectories(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).frameworkDirectories; } QHash CMakeManager::defines(KDevelop::ProjectBaseItem *item ) const { return fileInformation(item).defines; } QString CMakeManager::extraArguments(KDevelop::ProjectBaseItem *item) const { return fileInformation(item).compileFlags; } KDevelop::IProjectBuilder * CMakeManager::builder() const { IPlugin* i = core()->pluginController()->pluginForExtension( QStringLiteral("org.kdevelop.IProjectBuilder"), QStringLiteral("KDevCMakeBuilder")); Q_ASSERT(i); auto* _builder = i->extension(); Q_ASSERT(_builder ); return _builder ; } bool CMakeManager::reload(KDevelop::ProjectFolderItem* folder) { qCDebug(CMAKE) << "reloading" << folder->path(); IProject* project = folder->project(); if (!project->isReady()) return false; KJob *job = createImportJob(folder); project->setReloadJob(job); ICore::self()->runController()->registerJob( job ); if (folder == project->projectItem()) { connect(job, &KJob::finished, this, [project](KJob* job) { if (job->error()) return; + + KDevelop::ICore::self()->projectController()->projectConfigurationChanged(project); KDevelop::ICore::self()->projectController()->reparseProject(project, true); }); } return true; } static void populateTargets(ProjectFolderItem* folder, const QHash>& targets) { static QSet standardTargets = { QStringLiteral("edit_cache"), QStringLiteral("rebuild_cache"), QStringLiteral("list_install_components"), QStringLiteral("test"), //not really standard, but applicable for make and ninja QStringLiteral("install") }; QList dirTargets = kFilter>(targets[folder->path()], [](const CMakeTarget& target) -> bool { return target.type != CMakeTarget::Custom || (!target.name.endsWith(QLatin1String("_automoc")) && !target.name.endsWith(QLatin1String("_autogen")) && !standardTargets.contains(target.name) && !target.name.startsWith(QLatin1String("install/")) ); }); const auto tl = folder->targetList(); for (ProjectTargetItem* item : tl) { const auto idx = kIndexOf(dirTargets, [item](const CMakeTarget& target) { return target.name == item->text(); }); if (idx < 0) { delete item; } else { auto cmakeItem = dynamic_cast(item); if (cmakeItem) cmakeItem->setBuiltUrl(dirTargets[idx].artifacts.value(0)); dirTargets.removeAt(idx); } } foreach (const auto& target, dirTargets) { switch(target.type) { case CMakeTarget::Executable: new CMakeTargetItem(folder, target.name, target.artifacts.value(0)); break; case CMakeTarget::Library: new ProjectLibraryTargetItem(folder->project(), target.name, folder); break; case CMakeTarget::Custom: new ProjectTargetItem(folder->project(), target.name, folder); break; } } foreach (ProjectFolderItem* children, folder->folderList()) { populateTargets(children, targets); } } void CMakeManager::integrateData(const CMakeProjectData &data, KDevelop::IProject* project) { if (data.m_server) { connect(data.m_server.data(), &CMakeServer::response, project, [this, project](const QJsonObject& response) { serverResponse(project, response); }); } else { connect(data.watcher.data(), &QFileSystemWatcher::fileChanged, this, &CMakeManager::dirtyFile); connect(data.watcher.data(), &QFileSystemWatcher::directoryChanged, this, &CMakeManager::dirtyFile); } m_projects[project] = data; populateTargets(project->projectItem(), data.targets); CTestUtils::createTestSuites(data.m_testSuites, data.targets, project); } void CMakeManager::serverResponse(KDevelop::IProject* project, const QJsonObject& response) { if (response[QStringLiteral("type")] == QLatin1String("signal")) { if (response[QStringLiteral("name")] == QLatin1String("dirty")) { m_projects[project].m_server->configure({}); } else qCDebug(CMAKE) << "unhandled signal response..." << project << response; } else if (response[QStringLiteral("type")] == QLatin1String("reply")) { const auto inReplyTo = response[QStringLiteral("inReplyTo")]; if (inReplyTo == QLatin1String("configure")) { m_projects[project].m_server->compute(); } else if (inReplyTo == QLatin1String("compute")) { m_projects[project].m_server->codemodel(); } else if(inReplyTo == QLatin1String("codemodel")) { auto &data = m_projects[project]; CMakeServerImportJob::processCodeModel(response, data); populateTargets(project->projectItem(), data.targets); } else { qCDebug(CMAKE) << "unhandled reply response..." << project << response; } } else { qCDebug(CMAKE) << "unhandled response..." << project << response; } } // void CMakeManager::deletedWatchedDirectory(IProject* p, const QUrl &dir) // { // if(p->folder().equals(dir, QUrl::CompareWithoutTrailingSlash)) { // ICore::self()->projectController()->closeProject(p); // } else { // if(dir.fileName()=="CMakeLists.txt") { // QList folders = p->foldersForUrl(dir.upUrl()); // foreach(ProjectFolderItem* folder, folders) // reload(folder); // } else { // qDeleteAll(p->itemsForUrl(dir)); // } // } // } // void CMakeManager::directoryChanged(const QString& dir) // { // m_fileSystemChangedBuffer << dir; // m_fileSystemChangeTimer->start(); // } // void CMakeManager::filesystemBuffererTimeout() // { // Q_FOREACH(const QString& file, m_fileSystemChangedBuffer) { // realDirectoryChanged(file); // } // m_fileSystemChangedBuffer.clear(); // } // void CMakeManager::realDirectoryChanged(const QString& dir) // { // QUrl path(dir); // IProject* p=ICore::self()->projectController()->findProjectForUrl(dir); // if(!p || !p->isReady()) { // if(p) { // m_fileSystemChangedBuffer << dir; // m_fileSystemChangeTimer->start(); // } // return; // } // // if(!QFile::exists(dir)) { // path.adjustPath(QUrl::AddTrailingSlash); // deletedWatchedDirectory(p, path); // } else // dirtyFile(dir); // } QList< KDevelop::ProjectTargetItem * > CMakeManager::targets(KDevelop::ProjectFolderItem * folder) const { return folder->targetList(); } QString CMakeManager::name() const { return languageName().str(); } IndexedString CMakeManager::languageName() { static IndexedString name("CMake"); return name; } KDevelop::ParseJob * CMakeManager::createParseJob(const IndexedString &url) { return new CMakeParseJob(url, this); } KDevelop::ICodeHighlighting* CMakeManager::codeHighlighting() const { return m_highlight; } // ContextMenuExtension CMakeManager::contextMenuExtension( KDevelop::Context* context ) // { // if( context->type() != KDevelop::Context::ProjectItemContext ) // return IPlugin::contextMenuExtension( context ); // // KDevelop::ProjectItemContext* ctx = dynamic_cast( context ); // QList items = ctx->items(); // // if( items.isEmpty() ) // return IPlugin::contextMenuExtension( context ); // // m_clickedItems = items; // ContextMenuExtension menuExt; // if(items.count()==1 && dynamic_cast(items.first())) // { // QAction * action = new QAction( i18n( "Jump to Target Definition" ), this ); // connect( action, SIGNAL(triggered()), this, SLOT(jumpToDeclaration()) ); // menuExt.addAction( ContextMenuExtension::ProjectGroup, action ); // } // // return menuExt; // } // // void CMakeManager::jumpToDeclaration() // { // DUChainAttatched* du=dynamic_cast(m_clickedItems.first()); // if(du) // { // KTextEditor::Cursor c; // QUrl url; // { // KDevelop::DUChainReadLocker lock; // Declaration* decl = du->declaration().data(); // if(!decl) // return; // c = decl->rangeInCurrentRevision().start(); // url = decl->url().toUrl(); // } // // ICore::self()->documentController()->openDocument(url, c); // } // } // // // TODO: Port to Path API // bool CMakeManager::moveFilesAndFolders(const QList< ProjectBaseItem* > &items, ProjectFolderItem* toFolder) // { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Move files and folders within CMakeLists as follows:")); // // bool cmakeSuccessful = true; // CMakeFolderItem *nearestCMakeFolderItem = nearestCMakeFolder(toFolder); // IProject* project=toFolder->project(); // // QList movedUrls; // QList oldUrls; // foreach(ProjectBaseItem *movedItem, items) // { // QList dirtyItems = cmakeListedItemsAffectedByUrlChange(project, movedItem->url()); // QUrl movedItemNewUrl = toFolder->url(); // movedItemNewUrl.addPath(movedItem->baseName()); // if (movedItem->folder()) // movedItemNewUrl.adjustPath(QUrl::AddTrailingSlash); // foreach(ProjectBaseItem* dirtyItem, dirtyItems) // { // QUrl dirtyItemNewUrl = afterMoveUrl(dirtyItem->url(), movedItem->url(), movedItemNewUrl); // if (CMakeFolderItem* folder = dynamic_cast(dirtyItem)) // { // cmakeSuccessful &= changesWidgetRemoveCMakeFolder(folder, &changesWidget); // cmakeSuccessful &= changesWidgetAddFolder(dirtyItemNewUrl, nearestCMakeFolderItem, &changesWidget); // } // else if (dirtyItem->parent()->target()) // { // cmakeSuccessful &= changesWidgetMoveTargetFile(dirtyItem, dirtyItemNewUrl, &changesWidget); // } // } // // oldUrls += movedItem->url(); // movedUrls += movedItemNewUrl; // } // // if (changesWidget.hasDocuments() && cmakeSuccessful) // cmakeSuccessful &= changesWidget.exec() && changesWidget.applyAllChanges(); // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort move?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // QList::const_iterator it1=oldUrls.constBegin(), it1End=oldUrls.constEnd(); // QList::const_iterator it2=movedUrls.constBegin(); // Q_ASSERT(oldUrls.size()==movedUrls.size()); // for(; it1!=it1End; ++it1, ++it2) // { // if (!KDevelop::renameUrl(project, *it1, *it2)) // return false; // // QList renamedItems = project->itemsForUrl(*it2); // bool dir = QFileInfo(it2->toLocalFile()).isDir(); // foreach(ProjectBaseItem* item, renamedItems) { // if(dir) // emit folderRenamed(Path(*it1), item->folder()); // else // emit fileRenamed(Path(*it1), item->file()); // } // } // // return true; // } // // bool CMakeManager::copyFilesAndFolders(const KDevelop::Path::List &items, KDevelop::ProjectFolderItem* toFolder) // { // IProject* project = toFolder->project(); // foreach(const Path& path, items) { // if (!KDevelop::copyUrl(project, path.toUrl(), toFolder->url())) // return false; // } // // return true; // } // // bool CMakeManager::removeFilesAndFolders(const QList &items) // { // using namespace CMakeEdit; // // IProject* p = 0; // QList urls; // foreach(ProjectBaseItem* item, items) // { // Q_ASSERT(item->folder() || item->file()); // // urls += item->url(); // if(!p) // p = item->project(); // } // // //First do CMakeLists changes // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Remove files and folders from CMakeLists as follows:")); // // bool cmakeSuccessful = changesWidgetRemoveItems(cmakeListedItemsAffectedByItemsChanged(items).toSet(), &changesWidget); // // if (changesWidget.hasDocuments() && cmakeSuccessful) // cmakeSuccessful &= changesWidget.exec() && changesWidget.applyAllChanges(); // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort deletion?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // bool ret = true; // //Then delete the files/folders // foreach(const QUrl& file, urls) // { // ret &= KDevelop::removeUrl(p, file, QDir(file.toLocalFile()).exists()); // } // // return ret; // } bool CMakeManager::removeFilesFromTargets(const QList &/*files*/) { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Modify project targets as follows:")); // // if (!files.isEmpty() && // changesWidgetRemoveFilesFromTargets(files, &changesWidget) && // changesWidget.exec() && // changesWidget.applyAllChanges()) { // return true; // } return false; } // ProjectFolderItem* CMakeManager::addFolder(const Path& folder, ProjectFolderItem* parent) // { // using namespace CMakeEdit; // // CMakeFolderItem *cmakeParent = nearestCMakeFolder(parent); // if(!cmakeParent) // return 0; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Create folder '%1':", folder.lastPathSegment())); // // ///FIXME: use path in changes widget // changesWidgetAddFolder(folder.toUrl(), cmakeParent, &changesWidget); // // if(changesWidget.exec() && changesWidget.applyAllChanges()) // { // if(KDevelop::createFolder(folder.toUrl())) { //If saved we create the folder then the CMakeLists.txt file // Path newCMakeLists(folder, "CMakeLists.txt"); // KDevelop::createFile( newCMakeLists.toUrl() ); // } else // KMessageBox::error(0, i18n("Could not save the change."), // DIALOG_CAPTION); // } // // return 0; // } // // KDevelop::ProjectFileItem* CMakeManager::addFile( const Path& file, KDevelop::ProjectFolderItem* parent) // { // KDevelop::ProjectFileItem* created = 0; // if ( KDevelop::createFile(file.toUrl()) ) { // QList< ProjectFileItem* > files = parent->project()->filesForPath(IndexedString(file.pathOrUrl())); // if(!files.isEmpty()) // created = files.first(); // else // created = new KDevelop::ProjectFileItem( parent->project(), file, parent ); // } // return created; // } bool CMakeManager::addFilesToTarget(const QList< ProjectFileItem* > &/*_files*/, ProjectTargetItem* /*target*/) { return false; // using namespace CMakeEdit; // // const QSet headerExt = QSet() << ".h" << ".hpp" << ".hxx"; // QList< ProjectFileItem* > files = _files; // for (int i = files.count() - 1; i >= 0; --i) // { // QString fileName = files[i]->fileName(); // QString fileExt = fileName.mid(fileName.lastIndexOf('.')); // QList sameUrlItems = files[i]->project()->itemsForUrl(files[i]->url()); // if (headerExt.contains(fileExt)) // files.removeAt(i); // else foreach(ProjectBaseItem* item, sameUrlItems) // { // if (item->parent() == target) // { // files.removeAt(i); // break; // } // } // } // // if(files.isEmpty()) // return true; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Modify target '%1' as follows:", target->baseName())); // // bool success = changesWidgetAddFilesToTarget(files, target, &changesWidget) && // changesWidget.exec() && // changesWidget.applyAllChanges(); // // if(!success) // KMessageBox::error(0, i18n("CMakeLists changes failed."), DIALOG_CAPTION); // // return success; } // bool CMakeManager::renameFileOrFolder(ProjectBaseItem *item, const Path &newPath) // { // using namespace CMakeEdit; // // ApplyChangesWidget changesWidget; // changesWidget.setCaption(DIALOG_CAPTION); // changesWidget.setInformation(i18n("Rename '%1' to '%2':", item->text(), // newPath.lastPathSegment())); // // bool cmakeSuccessful = true, changedCMakeLists=false; // IProject* project=item->project(); // const Path oldPath=item->path(); // QUrl oldUrl=oldPath.toUrl(); // if (item->file()) // { // QList targetFiles = cmakeListedItemsAffectedByUrlChange(project, oldUrl); // foreach(ProjectBaseItem* targetFile, targetFiles) // ///FIXME: use path in changes widget // cmakeSuccessful &= changesWidgetMoveTargetFile(targetFile, newPath.toUrl(), &changesWidget); // } // else if (CMakeFolderItem *folder = dynamic_cast(item)) // ///FIXME: use path in changes widget // cmakeSuccessful &= changesWidgetRenameFolder(folder, newPath.toUrl(), &changesWidget); // // item->setPath(newPath); // if (changesWidget.hasDocuments() && cmakeSuccessful) { // changedCMakeLists = changesWidget.exec() && changesWidget.applyAllChanges(); // cmakeSuccessful &= changedCMakeLists; // } // // if (!cmakeSuccessful) // { // if (KMessageBox::questionYesNo( QApplication::activeWindow(), // i18n("Changes to CMakeLists failed, abort rename?"), // DIALOG_CAPTION ) == KMessageBox::Yes) // return false; // } // // bool ret = KDevelop::renameUrl(project, oldUrl, newPath.toUrl()); // if(!ret) { // item->setPath(oldPath); // } // return ret; // } // // bool CMakeManager::renameFile(ProjectFileItem *item, const Path &newPath) // { // return renameFileOrFolder(item, newPath); // } // // bool CMakeManager::renameFolder(ProjectFolderItem* item, const Path &newPath) // { // return renameFileOrFolder(item, newPath); // } KTextEditor::Range CMakeManager::termRangeAtPosition(const KTextEditor::Document* textDocument, const KTextEditor::Cursor& position) const { const KTextEditor::Cursor step(0, 1); enum ParseState { NoChar, NonLeadingChar, AnyChar, }; ParseState parseState = NoChar; KTextEditor::Cursor start = position; while (true) { const QChar c = textDocument->characterAt(start); if (c.isDigit()) { parseState = NonLeadingChar; } else if (c.isLetter() || c == QLatin1Char('_')) { parseState = AnyChar; } else { // also catches going out of document range, where c is invalid break; } start -= step; } if (parseState != AnyChar) { return KTextEditor::Range::invalid(); } // undo step before last valid char start += step; KTextEditor::Cursor end = position + step; while (true) { const QChar c = textDocument->characterAt(end); if (!(c.isDigit() || c.isLetter() || c == QLatin1Char('_'))) { // also catches going out of document range, where c is invalid break; } end += step; } return KTextEditor::Range(start, end); } QPair CMakeManager::specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) { KTextEditor::Range itemRange; CMakeNavigationWidget* doc = nullptr; KDevelop::TopDUContextPointer top= TopDUContextPointer(KDevelop::DUChain::self()->chainForDocument(url)); if(top) { int useAt=top->findUseAt(top->transformToLocalRevision(position)); if(useAt>=0) { Use u=top->uses()[useAt]; doc = new CMakeNavigationWidget(top, u.usedDeclaration(top->topContext())); itemRange = u.m_range.castToSimpleRange(); } } if (!doc) { ICMakeDocumentation* docu=CMake::cmakeDocumentation(); if( docu ) { const auto* document = ICore::self()->documentController()->documentForUrl(url); const auto* textDocument = document->textDocument(); itemRange = termRangeAtPosition(textDocument, position); if (itemRange.isValid()) { const auto id = textDocument->text(itemRange); if (!id.isEmpty()) { IDocumentation::Ptr desc=docu->description(id, url); if (desc) { doc=new CMakeNavigationWidget(top, desc); } } } } } return {doc, itemRange}; } QPair CMakeManager::cacheValue(KDevelop::IProject* /*project*/, const QString& /*id*/) const { return QPair(); } // { // QPair ret; // if(project==0 && !m_projectsData.isEmpty()) // { // project=m_projectsData.keys().first(); // } // // // qCDebug(CMAKE) << "cache value " << id << project << (m_projectsData.contains(project) && m_projectsData[project].cache.contains(id)); // CMakeProjectData* data = m_projectsData[project]; // if(data && data->cache.contains(id)) // { // const CacheEntry& e=data->cache.value(id); // ret.first=e.value; // ret.second=e.doc; // } // return ret; // }Add // void CMakeManager::projectClosing(IProject* p) { m_projects.remove(p); // delete m_projectsData.take(p); // delete m_watchers.take(p); // // m_filter->remove(p); // // qCDebug(CMAKE) << "Project closed" << p; } // // QStringList CMakeManager::processGeneratorExpression(const QStringList& expr, IProject* project, ProjectTargetItem* target) const // { // QStringList ret; // const CMakeProjectData* data = m_projectsData[project]; // GenerationExpressionSolver exec(data->properties, data->targetAlias); // if(target) // exec.setTargetName(target->text()); // // exec.defineVariable("INSTALL_PREFIX", data->vm.value("CMAKE_INSTALL_PREFIX").join(QString())); // for(QStringList::const_iterator it = expr.constBegin(), itEnd = expr.constEnd(); it!=itEnd; ++it) { // QStringList val = exec.run(*it).split(';'); // ret += val; // } // return ret; // } /* void CMakeManager::addPending(const Path& path, CMakeFolderItem* folder) { m_pending.insert(path, folder); } CMakeFolderItem* CMakeManager::takePending(const Path& path) { return m_pending.take(path); } void CMakeManager::addWatcher(IProject* p, const QString& path) { if (QFileSystemWatcher* watcher = m_watchers.value(p)) { watcher->addPath(path); } else { qCWarning(CMAKE) << "Could not find a watcher for project" << p << p->name() << ", path " << path; Q_ASSERT(false); } }*/ // CMakeProjectData CMakeManager::projectData(IProject* project) // { // Q_ASSERT(QThread::currentThread() == project->thread()); // CMakeProjectData* data = m_projectsData[project]; // if(!data) { // data = new CMakeProjectData; // m_projectsData[project] = data; // } // return *data; // } ProjectFilterManager* CMakeManager::filterManager() const { return m_filter; } void CMakeManager::dirtyFile(const QString& path) { qCDebug(CMAKE) << "dirty!" << path; //we initialize again hte project that sent the signal for(QHash::const_iterator it = m_projects.constBegin(), itEnd = m_projects.constEnd(); it!=itEnd; ++it) { if(it->watcher == sender()) { reload(it.key()->projectItem()); break; } } } void CMakeManager::folderAdded(KDevelop::ProjectFolderItem* folder) { populateTargets(folder, m_projects[folder->project()].targets); } ProjectFolderItem* CMakeManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { // TODO: when we have data about targets, use folders with targets or similar if (QFile::exists(path.toLocalFile()+QLatin1String("/CMakeLists.txt"))) return new KDevelop::ProjectBuildFolderItem( project, path, parent ); else return KDevelop::AbstractFileManagerPlugin::createFolderItem(project, path, parent); } int CMakeManager::perProjectConfigPages() const { return 1; } ConfigPage* CMakeManager::perProjectConfigPage(int number, const ProjectConfigOptions& options, QWidget* parent) { if (number == 0) { return new CMakePreferences(this, options, parent); } return nullptr; } void CMakeManager::reloadProjects() { const auto& projects = m_projects.keys(); for (IProject* project : projects) { CMake::checkForNeedingConfigure(project); reload(project->projectItem()); } } +CMakeTarget CMakeManager::targetInformation(KDevelop::ProjectTargetItem* item) const +{ + const auto targets = m_projects[item->project()].targets[item->parent()->path()]; + for (auto target: targets) { + if (item->text() == target.name) { + return target; + } + } + return {}; +} + +KDevelop::Path CMakeManager::compiler(KDevelop::ProjectTargetItem* item) const +{ + const auto targetInfo = targetInformation(item); + if (targetInfo.sources.isEmpty()) { + qCDebug(CMAKE) << "could not find target" << item->text(); + return {}; + } + + const auto info = m_projects[item->project()].compilationData.files[targetInfo.sources.constFirst()]; + const auto lang = info.language; + if (lang.isEmpty()) { + qCDebug(CMAKE) << "no language for" << item << item->text() << info.defines << targetInfo.sources.constFirst(); + return {}; + } + const QString var = QLatin1String("CMAKE_") + lang + QLatin1String("_COMPILER"); + const auto ret = CMake::readCacheValues(KDevelop::Path(buildDirectory(item), QStringLiteral("CMakeCache.txt")), {var}); + qCDebug(CMAKE) << "compiler for" << lang << var << ret; + return KDevelop::Path(ret.value(var)); +} + #include "cmakemanager.moc" diff --git a/plugins/cmake/cmakemanager.h b/plugins/cmake/cmakemanager.h index d690e22f61..1a9a348cdd 100644 --- a/plugins/cmake/cmakemanager.h +++ b/plugins/cmake/cmakemanager.h @@ -1,170 +1,173 @@ /* KDevelop CMake Support * * Copyright 2006 Matt Rogers * Copyright 2007-2009 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. */ #ifndef CMAKEMANAGER_H #define CMAKEMANAGER_H #include #include #include #include #include #include #include #include #include "cmakeprojectdata.h" #include "icmakemanager.h" class WaitAllJobs; class CMakeCommitChangesJob; struct CMakeProjectData; class QObject; class CMakeHighlighting; class CMakeDocumentation; namespace KDevelop { class IProject; class IProjectBuilder; class ICodeHighlighting; class ProjectFolderItem; class ProjectBaseItem; class ProjectFileItem; class ProjectTargetItem; class ProjectFilterManager; class IProjectFilter; class ParseJob; class ContextMenuExtension; class Context; class IRuntime; } class CMakeFolderItem; class CMakeManager : public KDevelop::AbstractFileManagerPlugin , public KDevelop::IBuildSystemManager , public KDevelop::ILanguageSupport , public ICMakeManager { Q_OBJECT Q_INTERFACES( KDevelop::IBuildSystemManager ) Q_INTERFACES( KDevelop::IProjectFileManager ) Q_INTERFACES( KDevelop::ILanguageSupport ) Q_INTERFACES( ICMakeManager ) public: explicit CMakeManager( QObject* parent = nullptr, const QVariantList& args = QVariantList() ); ~CMakeManager() override; Features features() const override { return Features(Folders | Targets | Files ); } KDevelop::IProjectBuilder* builder() const override; bool hasBuildInfo(KDevelop::ProjectBaseItem*) const override; KDevelop::Path buildDirectory(KDevelop::ProjectBaseItem*) const override; KDevelop::Path::List includeDirectories(KDevelop::ProjectBaseItem *) const override; KDevelop::Path::List frameworkDirectories(KDevelop::ProjectBaseItem *item) const override; QHash defines(KDevelop::ProjectBaseItem *) const override; QString extraArguments(KDevelop::ProjectBaseItem *item) const override; KDevelop::ProjectTargetItem* createTarget( const QString&, KDevelop::ProjectFolderItem* ) override { return nullptr; } virtual QList targets() const; QList targets(KDevelop::ProjectFolderItem* folder) const override; // virtual KDevelop::ProjectFolderItem* addFolder( const KDevelop::Path& folder, KDevelop::ProjectFolderItem* parent ); // virtual KDevelop::ProjectFileItem* addFile( const KDevelop::Path&, KDevelop::ProjectFolderItem* ); bool addFilesToTarget( const QList &files, KDevelop::ProjectTargetItem* target) override; bool removeTarget( KDevelop::ProjectTargetItem* ) override { return false; } bool removeFilesFromTargets( const QList &files ) override; // virtual bool removeFilesAndFolders( const QList &items); // // virtual bool renameFile(KDevelop::ProjectFileItem*, const KDevelop::Path&); // virtual bool renameFolder(KDevelop::ProjectFolderItem*, const KDevelop::Path&); // virtual bool moveFilesAndFolders(const QList< KDevelop::ProjectBaseItem* > &items, KDevelop::ProjectFolderItem *newParent); // virtual bool copyFilesAndFolders(const KDevelop::Path::List &items, KDevelop::ProjectFolderItem* newParent); // // virtual QList parse( KDevelop::ProjectFolderItem* dom ); KDevelop::ProjectFolderItem* import( KDevelop::IProject *project ) override; KJob* createImportJob(KDevelop::ProjectFolderItem* item) override; // bool reload(KDevelop::ProjectFolderItem*) override; // // virtual KDevelop::ContextMenuExtension contextMenuExtension( KDevelop::Context* context ); KDevelop::ProjectFolderItem* createFolderItem(KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent = nullptr) override; QPair cacheValue(KDevelop::IProject* project, const QString& id) const override; //LanguageSupport QString name() const override; KDevelop::ParseJob *createParseJob(const KDevelop::IndexedString &url) override; KDevelop::ICodeHighlighting* codeHighlighting() const override; QPair specialLanguageObjectNavigationWidget(const QUrl& url, const KTextEditor::Cursor& position) override; // void addPending(const KDevelop::Path& path, CMakeFolderItem* folder); // CMakeFolderItem* takePending(const KDevelop::Path& path); // void addWatcher(KDevelop::IProject* p, const QString& path); // CMakeProjectData projectData(KDevelop::IProject* project); KDevelop::ProjectFilterManager* filterManager() const; static KDevelop::IndexedString languageName(); int perProjectConfigPages() const override; KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; void integrateData(const CMakeProjectData &data, KDevelop::IProject* project); + KDevelop::Path compiler(KDevelop::ProjectTargetItem * p) const override; + Q_SIGNALS: void folderRenamed(const KDevelop::Path& oldFolder, KDevelop::ProjectFolderItem* newFolder); void fileRenamed(const KDevelop::Path& oldFile, KDevelop::ProjectFileItem* newFile); private Q_SLOTS: void serverResponse(KDevelop::IProject* project, const QJsonObject &value); // void jumpToDeclaration(); void projectClosing(KDevelop::IProject*); void dirtyFile(const QString& file); // // void directoryChanged(const QString& dir); // void filesystemBuffererTimeout(); private: void reloadProjects(); CMakeFile fileInformation(KDevelop::ProjectBaseItem* item) const; + CMakeTarget targetInformation(KDevelop::ProjectTargetItem* item) const; void folderAdded(KDevelop::ProjectFolderItem* folder); KTextEditor::Range termRangeAtPosition(const KTextEditor::Document* textDocument, const KTextEditor::Cursor& position) const; private: QHash m_projects; KDevelop::ProjectFilterManager* m_filter; KDevelop::ICodeHighlighting* m_highlight; }; #endif diff --git a/plugins/cmake/cmakeprojectdata.h b/plugins/cmake/cmakeprojectdata.h index 481055bdfa..4b7ac4dde1 100644 --- a/plugins/cmake/cmakeprojectdata.h +++ b/plugins/cmake/cmakeprojectdata.h @@ -1,113 +1,114 @@ /* KDevelop CMake Support * * Copyright 2013-2017 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. */ #ifndef CMAKEPROJECTDATA_H #define CMAKEPROJECTDATA_H #include #include #include #include #include class CMakeServer; /** * Represents any file in a cmake project that has been added * to the project. * * Contains the required information to compile it properly */ struct CMakeFile { KDevelop::Path::List includes; KDevelop::Path::List frameworkDirectories; QString compileFlags; + QString language; QHash defines; bool isEmpty() const { return includes.isEmpty() && frameworkDirectories.isEmpty() && compileFlags.isEmpty() && defines.isEmpty(); } }; inline QDebug &operator<<(QDebug debug, const CMakeFile& file) { - debug << "CMakeFile(-I" << file.includes << ", -F" << file.frameworkDirectories << ", -D" << file.defines << ")"; + debug << "CMakeFile(-I" << file.includes << ", -F" << file.frameworkDirectories << ", -D" << file.defines << ", " << file.language << ")"; return debug.maybeSpace(); } struct CMakeFilesCompilationData { QHash files; bool isValid = false; }; struct CMakeTarget { Q_GADGET public: enum Type { Library, Executable, Custom }; Q_ENUM(Type) Type type; QString name; KDevelop::Path::List artifacts; KDevelop::Path::List sources; }; Q_DECLARE_TYPEINFO(CMakeTarget, Q_MOVABLE_TYPE); inline QDebug &operator<<(QDebug debug, const CMakeTarget& target) { debug << target.type << ':' << target.name; return debug.maybeSpace(); } inline bool operator==(const CMakeTarget& lhs, const CMakeTarget& rhs) { return lhs.type == rhs.type && lhs.name == rhs.name && lhs.artifacts == rhs.artifacts; } struct Test { Test() {} QString name; QString executable; QStringList arguments; QHash properties; }; Q_DECLARE_TYPEINFO(Test, Q_MOVABLE_TYPE); struct CMakeProjectData { CMakeProjectData(const QHash> &targets, const CMakeFilesCompilationData &data, const QVector &tests); CMakeProjectData() : watcher(new QFileSystemWatcher) {} ~CMakeProjectData() {} CMakeFilesCompilationData compilationData; QHash> targets; QSharedPointer watcher; QSharedPointer m_server; QVector m_testSuites; }; #endif diff --git a/plugins/cmake/cmakeserverimportjob.cpp b/plugins/cmake/cmakeserverimportjob.cpp index f46596d280..54cbf92f60 100644 --- a/plugins/cmake/cmakeserverimportjob.cpp +++ b/plugins/cmake/cmakeserverimportjob.cpp @@ -1,225 +1,226 @@ /* KDevelop CMake Support * * Copyright 2017 Aleix Pol Gonzalez * * 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 "cmakeserverimportjob.h" #include "cmakeutils.h" #include "cmakeserver.h" #include #include #include #include #include #include #include #include #include #include "debug.h" static QString unescape(const QStringRef& input) { QString output; output.reserve(input.length()); bool isEscaped = false; for (auto it = input.data(), end = it + input.length(); it != end; ++it) { QChar c = *it; if (!isEscaped && c == QLatin1Char('\\')) { isEscaped = true; } else { output.append(c); isEscaped = false; } } return output; } static QHash processDefines(const QString &compileFlags, const QJsonArray &defines) { QHash ret; const auto& defineRx = MakeFileResolver::defineRegularExpression(); auto it = defineRx.globalMatch(compileFlags); while (it.hasNext()) { const auto match = it.next(); QString value; if (match.lastCapturedIndex() > 1) { value = unescape(match.capturedRef(match.lastCapturedIndex())); } ret[match.captured(1)] = value; } for (const QJsonValue& defineValue: defines) { const QString define = defineValue.toString(); const int eqIdx = define.indexOf(QLatin1Char('=')); if (eqIdx<0) { ret[define] = QString(); } else { ret[define.left(eqIdx)] = define.mid(eqIdx+1); } } return ret; } CMakeTarget::Type typeToEnum(const QJsonObject& target) { static const QHash s_types = { {QStringLiteral("EXECUTABLE"), CMakeTarget::Executable}, {QStringLiteral("STATIC_LIBRARY"), CMakeTarget::Library}, {QStringLiteral("MODULE_LIBRARY"), CMakeTarget::Library}, {QStringLiteral("SHARED_LIBRARY"), CMakeTarget::Library}, {QStringLiteral("OBJECT_LIBRARY"), CMakeTarget::Library}, {QStringLiteral("INTERFACE_LIBRARY"), CMakeTarget::Library} }; const auto value = target.value(QLatin1String("type")).toString(); return s_types.value(value, CMakeTarget::Custom); } void CMakeServerImportJob::processCodeModel(const QJsonObject &response, CMakeProjectData &data) { const auto configs = response.value(QStringLiteral("configurations")).toArray(); qCDebug(CMAKE) << "process response" << response; data.targets.clear(); data.compilationData.files.clear(); const auto rt = KDevelop::ICore::self()->runtimeController()->currentRuntime(); for (const auto &config: configs) { const auto projects = config.toObject().value(QStringLiteral("projects")).toArray(); for (const auto &project: projects) { const auto targets = project.toObject().value(QStringLiteral("targets")).toArray(); for (const auto &targetObject: targets) { const auto target = targetObject.toObject(); const KDevelop::Path targetDir = rt->pathInHost(KDevelop::Path(target.value(QStringLiteral("sourceDirectory")).toString())); KDevelop::Path::List targetSources; const auto fileGroups = target.value(QStringLiteral("fileGroups")).toArray(); for (const auto &fileGroupValue: fileGroups) { const auto fileGroup = fileGroupValue.toObject(); CMakeFile file; file.includes = kTransform(fileGroup.value(QStringLiteral("includePath")).toArray(), [](const QJsonValue& val) { return KDevelop::Path(val.toObject().value(QStringLiteral("path")).toString()); }); + file.language = fileGroup.value(QStringLiteral("language")).toString(), file.compileFlags = fileGroup.value(QStringLiteral("compileFlags")).toString(); file.defines = processDefines(file.compileFlags, fileGroup.value(QStringLiteral("defines")).toArray()); // apparently some file groups do not contain build system information // skip these, as they would produce bogus results for us and break the fallback // implemented in CMakeManager::fileInformation if (file.isEmpty()) { continue; } const auto sourcesArray = fileGroup.value(QStringLiteral("sources")).toArray(); const KDevelop::Path::List sources = kTransform(sourcesArray, [targetDir](const QJsonValue& val) { return KDevelop::Path(targetDir, val.toString()); }); targetSources.reserve(targetSources.size() + sources.size()); for (const auto& source: sources) { // NOTE: we use the canonical file path to prevent issues with symlinks in the path // leading to lookup failures const auto localFile = rt->pathInHost(source); const auto canonicalFile = QFileInfo(source.toLocalFile()).canonicalFilePath(); const auto sourcePath = localFile.toLocalFile() == canonicalFile ? localFile : KDevelop::Path(canonicalFile); data.compilationData.files[sourcePath] = file; targetSources << sourcePath; } qCDebug(CMAKE) << "registering..." << sources << file; } CMakeTarget cmakeTarget{ typeToEnum(target), target.value(QStringLiteral("name")).toString(), kTransform(target[QLatin1String("artifacts")].toArray(), [](const QJsonValue& val) { return KDevelop::Path(val.toString()); }), targetSources, }; // ensure we don't add the same target multiple times, for different projects // cf.: https://bugs.kde.org/show_bug.cgi?id=387095 auto& dirTargets = data.targets[targetDir]; if (dirTargets.contains(cmakeTarget)) continue; dirTargets += cmakeTarget; qCDebug(CMAKE) << "adding target" << cmakeTarget.name << "with sources" << cmakeTarget.sources; } } } } CMakeServerImportJob::CMakeServerImportJob(KDevelop::IProject* project, const QSharedPointer &server, QObject* parent) : KJob(parent) , m_server(server) , m_project(project) { connect(m_server.data(), &CMakeServer::disconnected, this, [this]() { setError(UnexpectedDisconnect); emitResult(); }); } void CMakeServerImportJob::start() { if (m_server->isServerAvailable()) doStart(); else connect(m_server.data(), &CMakeServer::connected, this, &CMakeServerImportJob::doStart); } void CMakeServerImportJob::doStart() { connect(m_server.data(), &CMakeServer::response, this, &CMakeServerImportJob::processResponse); m_server->handshake(m_project->path(), CMake::currentBuildDir(m_project)); } void CMakeServerImportJob::processResponse(const QJsonObject& response) { const auto responseType = response.value(QStringLiteral("type")); if (responseType == QLatin1String("reply")) { const auto inReplyTo = response.value(QStringLiteral("inReplyTo")); qCDebug(CMAKE) << "replying..." << inReplyTo; if (inReplyTo == QLatin1String("handshake")) { m_server->configure({}); } else if (inReplyTo == QLatin1String("configure")) { m_server->compute(); } else if (inReplyTo == QLatin1String("compute")) { m_server->codemodel(); } else if(inReplyTo == QLatin1String("codemodel")) { processCodeModel(response, m_data); m_data.m_testSuites = CMake::importTestSuites(CMake::currentBuildDir(m_project)); m_data.m_server = m_server; emitResult(); } else { qCDebug(CMAKE) << "unhandled reply" << response; } } else if(responseType == QLatin1String("error")) { setError(ErrorResponse); setErrorText(response.value(QStringLiteral("errorMessage")).toString()); qCWarning(CMAKE) << "error!!" << response; emitResult(); } else if (responseType == QLatin1String("progress")) { int progress = response.value(QStringLiteral("progressCurrent")).toInt(); int total = response.value(QStringLiteral("progressMaximum")).toInt(); if (progress >= 0 && total > 0) { setPercent(100.0 * progress / total); } } else if (responseType == QLatin1String("message") || responseType == QLatin1String("hello")) { // Known, but not used for anything currently. } else { qCDebug(CMAKE) << "unhandled message" << response; } } diff --git a/plugins/custom-buildsystem/custombuildsystemplugin.cpp b/plugins/custom-buildsystem/custombuildsystemplugin.cpp index db5dfc91c2..695372dbab 100644 --- a/plugins/custom-buildsystem/custombuildsystemplugin.cpp +++ b/plugins/custom-buildsystem/custombuildsystemplugin.cpp @@ -1,198 +1,203 @@ /************************************************************************ * KDevelop4 Custom Buildsystem Support * * * * Copyright 2010 Andreas Pakulat * * * * 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 or version 3 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, see . * ************************************************************************/ #include "custombuildsystemplugin.h" #include #include #include #include #include "configconstants.h" #include "kcm_custombuildsystem.h" #include "custombuildjob.h" using KDevelop::ProjectTargetItem; using KDevelop::ProjectFolderItem; using KDevelop::ProjectBuildFolderItem; using KDevelop::ProjectBaseItem; using KDevelop::ProjectFileItem; using KDevelop::IPlugin; using KDevelop::ICore; using KDevelop::IOutputView; using KDevelop::IProjectFileManager; using KDevelop::IProjectBuilder; using KDevelop::IProject; using KDevelop::Path; K_PLUGIN_FACTORY_WITH_JSON(CustomBuildSystemFactory, "kdevcustombuildsystem.json", registerPlugin(); ) CustomBuildSystem::CustomBuildSystem( QObject *parent, const QVariantList & ) : AbstractFileManagerPlugin( QStringLiteral("kdevcustombuildsystem"), parent ) { } CustomBuildSystem::~CustomBuildSystem() { } bool CustomBuildSystem::addFilesToTarget( const QList&, ProjectTargetItem* ) { return false; } bool CustomBuildSystem::hasBuildInfo( ProjectBaseItem* ) const { return false; } KJob* CustomBuildSystem::build( ProjectBaseItem* dom ) { return new CustomBuildJob( this, dom, CustomBuildSystemTool::Build ); } Path CustomBuildSystem::buildDirectory( ProjectBaseItem* item ) const { Path p; if( item->folder() ) { p = item->path(); } else { ProjectBaseItem* parent = item; while( !parent->folder() ) { parent = parent->parent(); } p = parent->path(); } const QString relative = item->project()->path().relativePath(p); KConfigGroup grp = configuration( item->project() ); if(!grp.isValid()) { return Path(); } Path builddir(grp.readEntry(ConfigConstants::buildDirKey(), QUrl())); if(!builddir.isValid() ) // set builddir to default if project contains a buildDirKey that does not have a value { builddir = item->project()->path(); } builddir.addPath( relative ); return builddir; } IProjectBuilder* CustomBuildSystem::builder() const { return const_cast(dynamic_cast(this)); } KJob* CustomBuildSystem::clean( ProjectBaseItem* dom ) { return new CustomBuildJob( this, dom, CustomBuildSystemTool::Clean ); } KJob* CustomBuildSystem::configure( IProject* project ) { return new CustomBuildJob( this, project->projectItem(), CustomBuildSystemTool::Configure ); } ProjectTargetItem* CustomBuildSystem::createTarget( const QString&, ProjectFolderItem* ) { return nullptr; } QHash CustomBuildSystem::defines( ProjectBaseItem* ) const { return {}; } IProjectFileManager::Features CustomBuildSystem::features() const { return IProjectFileManager::Files | IProjectFileManager::Folders; } ProjectFolderItem* CustomBuildSystem::createFolderItem( IProject* project, const Path& path, ProjectBaseItem* parent ) { return new ProjectBuildFolderItem( project, path, parent ); } Path::List CustomBuildSystem::includeDirectories( ProjectBaseItem* ) const { return {}; } Path::List CustomBuildSystem::frameworkDirectories( ProjectBaseItem* ) const { return {}; } QString CustomBuildSystem::extraArguments(KDevelop::ProjectBaseItem*) const { return {}; } KJob* CustomBuildSystem::install( KDevelop::ProjectBaseItem* item, const QUrl &installPrefix ) { auto job = new CustomBuildJob( this, item, CustomBuildSystemTool::Install ); job->setInstallPrefix(installPrefix); return job; } KJob* CustomBuildSystem::prune( IProject* project ) { return new CustomBuildJob( this, project->projectItem(), CustomBuildSystemTool::Prune ); } bool CustomBuildSystem::removeFilesFromTargets( const QList& ) { return false; } bool CustomBuildSystem::removeTarget( ProjectTargetItem* ) { return false; } QList CustomBuildSystem::targets( ProjectFolderItem* ) const { return QList(); } KConfigGroup CustomBuildSystem::configuration( IProject* project ) const { KConfigGroup grp = project->projectConfiguration()->group(ConfigConstants::customBuildSystemGroup()); if (grp.isValid() && grp.hasKey(ConfigConstants::currentConfigKey())) return grp.group(grp.readEntry(ConfigConstants::currentConfigKey())); else return KConfigGroup(); } int CustomBuildSystem::perProjectConfigPages() const { return 1; } KDevelop::ConfigPage* CustomBuildSystem::perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) { if (number == 0) { return new CustomBuildSystemKCModule(this, options, parent); } return nullptr; } +KDevelop::Path CustomBuildSystem::compiler(KDevelop::ProjectTargetItem* item) const +{ + return {}; +} + #include "custombuildsystemplugin.moc" diff --git a/plugins/custom-buildsystem/custombuildsystemplugin.h b/plugins/custom-buildsystem/custombuildsystemplugin.h index 694b1f231e..89b2ef4df9 100644 --- a/plugins/custom-buildsystem/custombuildsystemplugin.h +++ b/plugins/custom-buildsystem/custombuildsystemplugin.h @@ -1,92 +1,95 @@ /************************************************************************ * KDevelop4 Custom Buildsystem Support * * * * Copyright 2010 Andreas Pakulat * * * * 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 or version 3 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, see . * ************************************************************************/ #ifndef CUSTOMBUILDSYSTEMPLUGIN_H #define CUSTOMBUILDSYSTEMPLUGIN_H #include #include #include #include class KConfigGroup; class KDialogBase; namespace KDevelop { class ProjectBaseItem; class IProject; } class CustomBuildSystem : public KDevelop::AbstractFileManagerPlugin, public KDevelop::IProjectBuilder, public KDevelop::IBuildSystemManager { Q_OBJECT Q_INTERFACES( KDevelop::IProjectBuilder ) Q_INTERFACES( KDevelop::IProjectFileManager ) Q_INTERFACES( KDevelop::IBuildSystemManager ) public: explicit CustomBuildSystem( QObject *parent = nullptr, const QVariantList &args = QVariantList() ); ~CustomBuildSystem() override; // ProjectBuilder API KJob* build( KDevelop::ProjectBaseItem* dom ) override; KJob* clean( KDevelop::ProjectBaseItem* dom ) override; KJob* prune( KDevelop::IProject* ) override; /// @p installPrefix will be passed as DESTDIR environment variable KJob* install( KDevelop::ProjectBaseItem* item, const QUrl &installPrefix ) override; KJob* configure( KDevelop::IProject* ) override; + + KDevelop::Path compiler(KDevelop::ProjectTargetItem * p) const override; + Q_SIGNALS: void built( KDevelop::ProjectBaseItem *dom ); void installed( KDevelop::ProjectBaseItem* ); void cleaned( KDevelop::ProjectBaseItem* ); void failed( KDevelop::ProjectBaseItem *dom ); void configured( KDevelop::IProject* ); void pruned( KDevelop::IProject* ); // AbstractFileManagerPlugin API public: Features features() const override; KDevelop::ProjectFolderItem* createFolderItem( KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent = nullptr ) override; // BuildSystemManager API public: bool addFilesToTarget( const QList& file, KDevelop::ProjectTargetItem* parent ) override; bool hasBuildInfo( KDevelop::ProjectBaseItem* ) const override; KDevelop::Path buildDirectory( KDevelop::ProjectBaseItem* ) const override; IProjectBuilder* builder() const override; KDevelop::ProjectTargetItem* createTarget( const QString& target, KDevelop::ProjectFolderItem* parent ) override; QHash defines( KDevelop::ProjectBaseItem* ) const override; KDevelop::Path::List includeDirectories( KDevelop::ProjectBaseItem* ) const override; KDevelop::Path::List frameworkDirectories( KDevelop::ProjectBaseItem* ) const override; QString extraArguments(KDevelop::ProjectBaseItem *item) const override; bool removeFilesFromTargets( const QList& ) override; bool removeTarget( KDevelop::ProjectTargetItem* target ) override; QList targets( KDevelop::ProjectFolderItem* ) const override; KConfigGroup configuration( KDevelop::IProject* ) const; KConfigGroup findMatchingPathGroup( const KConfigGroup& cfg, KDevelop::ProjectBaseItem* item ) const; // IPlugin API public: int perProjectConfigPages() const override; KDevelop::ConfigPage* perProjectConfigPage(int number, const KDevelop::ProjectConfigOptions& options, QWidget* parent) override; }; #endif diff --git a/plugins/custom-definesandincludes/compilerprovider/compilerfactories.cpp b/plugins/custom-definesandincludes/compilerprovider/compilerfactories.cpp index 2f9a604bb9..fd44f3623e 100644 --- a/plugins/custom-definesandincludes/compilerprovider/compilerfactories.cpp +++ b/plugins/custom-definesandincludes/compilerprovider/compilerfactories.cpp @@ -1,79 +1,95 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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 "compilerfactories.h" #include "compilerprovider.h" #include "gcclikecompiler.h" #include "msvccompiler.h" QString ClangFactory::name() const { return QStringLiteral("Clang"); } +bool ClangFactory::isSupported(const KDevelop::Path& path) const +{ + const auto filename = path.lastPathSegment(); + return filename.contains(QLatin1String("clang")) && !filename.contains(QLatin1String("clang-cl")); +} + CompilerPointer ClangFactory::createCompiler(const QString& name, const QString& path, bool editable ) const { return CompilerPointer(new GccLikeCompiler(name, path, editable, this->name())); } void ClangFactory::registerDefaultCompilers(CompilerProvider* provider) const { const QString clang = QStringLiteral("clang"); auto compiler = createCompiler(name(), clang, false); provider->registerCompiler(compiler); } QString GccFactory::name() const { return QStringLiteral("GCC"); } +bool GccFactory::isSupported(const KDevelop::Path& path) const +{ + return path.lastPathSegment().contains(QLatin1String("gcc")) || path.lastPathSegment().contains(QLatin1String("g++")); +} + CompilerPointer GccFactory::createCompiler(const QString& name, const QString& path, bool editable ) const { return CompilerPointer(new GccLikeCompiler(name, path, editable, this->name())); } void GccFactory::registerDefaultCompilers(CompilerProvider* provider) const { const QString gcc = QStringLiteral("gcc"); auto compiler = createCompiler(name(), gcc, false); provider->registerCompiler(compiler); } QString MsvcFactory::name() const { return QStringLiteral("MSVC"); } CompilerPointer MsvcFactory::createCompiler(const QString& name, const QString& path, bool editable ) const { return CompilerPointer(new MsvcCompiler(name, path, editable, this->name())); } void MsvcFactory::registerDefaultCompilers(CompilerProvider* provider) const { provider->registerCompiler(createCompiler(name(), QStringLiteral("cl.exe"), false)); } + +bool MsvcFactory::isSupported(const KDevelop::Path& path) const +{ + return path.lastPathSegment() == QLatin1String("cl.exe") || path.lastPathSegment().contains(QLatin1String("clang-cl")); +} diff --git a/plugins/custom-definesandincludes/compilerprovider/compilerfactories.h b/plugins/custom-definesandincludes/compilerprovider/compilerfactories.h index 20efc62d86..c313619cee 100644 --- a/plugins/custom-definesandincludes/compilerprovider/compilerfactories.h +++ b/plugins/custom-definesandincludes/compilerprovider/compilerfactories.h @@ -1,59 +1,62 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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 . * */ #ifndef COMPILERFACTORIES_H #define COMPILERFACTORIES_H #include "icompilerfactory.h" class ClangFactory : public ICompilerFactory { public: CompilerPointer createCompiler( const QString& name, const QString& path, bool editable = true ) const override; QString name() const override; + bool isSupported(const KDevelop::Path& path) const override; void registerDefaultCompilers(CompilerProvider* provider) const override; }; class GccFactory : public ICompilerFactory { public: CompilerPointer createCompiler( const QString& name, const QString& path, bool editable = true ) const override; QString name() const override; + bool isSupported(const KDevelop::Path& path) const override; void registerDefaultCompilers(CompilerProvider* provider) const override; }; class MsvcFactory : public ICompilerFactory { public: CompilerPointer createCompiler( const QString& name, const QString& path, bool editable = true ) const override; QString name() const override; + bool isSupported(const KDevelop::Path& path) const override; void registerDefaultCompilers(CompilerProvider* provider) const override; }; #endif // COMPILERFACTORIES_H diff --git a/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp b/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp index 14d55062e2..86d986b4ff 100644 --- a/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp +++ b/plugins/custom-definesandincludes/compilerprovider/compilerprovider.cpp @@ -1,281 +1,333 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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 "compilerprovider.h" #include "debug.h" #include "compilerfactories.h" #include "settingsmanager.h" #include #include #include +#include +#include #include #include #include #include using namespace KDevelop; namespace { class NoCompiler : public ICompiler { public: NoCompiler(): ICompiler(i18n("None"), QString(), QString(), false) {} QHash< QString, QString > defines(Utils::LanguageType, const QString&) const override { return {}; } Path::List includes(Utils::LanguageType, const QString&) const override { return {}; } }; static CompilerPointer createDummyCompiler() { static CompilerPointer compiler(new NoCompiler()); return compiler; } ConfigEntry configForItem(KDevelop::ProjectBaseItem* item) { if(!item){ return ConfigEntry(); } const Path itemPath = item->path(); const Path rootDirectory = item->project()->path(); auto paths = SettingsManager::globalInstance()->readPaths(item->project()->projectConfiguration().data()); ConfigEntry config; Path closestPath; // find config entry closest to the requested item for (const auto& entry : paths) { auto configEntry = entry; Path targetDirectory = rootDirectory; targetDirectory.addPath(entry.path); if (targetDirectory == itemPath) { return configEntry; } if (targetDirectory.isParentOf(itemPath)) { if (config.path.isEmpty() || targetDirectory.segments().size() > closestPath.segments().size()) { config = configEntry; closestPath = targetDirectory; } } } return config; } } +ProjectTargetItem* findCompiledTarget(ProjectBaseItem* item) +{ + for(auto item: item->targetList()) { + if (item->type() == ProjectBaseItem::ExecutableTarget || item->type() == ProjectBaseItem::LibraryTarget) { + return item; + } + } + + for(auto folder: item->folderList()) { + auto target = findCompiledTarget(folder); + if (target) + return target; + } + return nullptr; +} + CompilerProvider::CompilerProvider( SettingsManager* settings, QObject* parent ) : QObject( parent ) , m_settings(settings) { m_factories = { CompilerFactoryPointer(new GccFactory()), CompilerFactoryPointer(new ClangFactory()), #ifdef _WIN32 CompilerFactoryPointer(new MsvcFactory()), #endif }; if (!QStandardPaths::findExecutable( QStringLiteral("clang") ).isEmpty()) { m_factories[1]->registerDefaultCompilers(this); } if (!QStandardPaths::findExecutable( QStringLiteral("gcc") ).isEmpty()) { m_factories[0]->registerDefaultCompilers(this); } #ifdef _WIN32 if (!QStandardPaths::findExecutable(QStringLiteral("cl.exe")).isEmpty()) { m_factories[2]->registerDefaultCompilers(this); } #endif registerCompiler(createDummyCompiler()); retrieveUserDefinedCompilers(); connect(ICore::self()->runtimeController(), &IRuntimeController::currentRuntimeChanged, this, [this]() { m_defaultProvider.clear(); }); + connect(ICore::self()->projectController(), &IProjectController::projectConfigurationChanged, this, &CompilerProvider::projectChanged); + connect(ICore::self()->projectController(), &IProjectController::projectOpened, this, &CompilerProvider::projectChanged); } CompilerProvider::~CompilerProvider() = default; +void CompilerProvider::projectChanged(KDevelop::IProject* p) +{ + const auto target = findCompiledTarget(p->projectItem()); + if (!target) + return; + + auto path = p->buildSystemManager()->compiler(target); + qCDebug(DEFINESANDINCLUDES) << "found compiler" << path; + if (path.isEmpty()) + return; + + Q_ASSERT(QDir::isAbsolutePath(path.toLocalFile())); + const auto pathString = path.toLocalFile(); + auto it = std::find_if(m_compilers.begin(), m_compilers.end(), + [pathString](const CompilerPointer& compiler) { return compiler->path() == pathString; }); + if (it != m_compilers.end()) { + m_defaultProvider = *it; + return; + } + + //we need to search, sdk compiler names are weird: arm-linux-androideabi-g++ + for (auto factory : m_factories) { + if (factory->isSupported(path)) { + auto compiler = factory->createCompiler(path.lastPathSegment(), pathString); + const auto registered = registerCompiler(compiler); + m_defaultProvider = compiler; + } + } + + qCDebug(DEFINESANDINCLUDES) << "using compiler" << m_defaultProvider << path; +} + QHash CompilerProvider::defines( const QString& path ) const { auto config = configForItem(nullptr); auto languageType = Utils::languageType(path, config.parserArguments.parseAmbiguousAsCPP); // If called on files that we can't compile, return an empty set of defines. if (languageType == Utils::Other) { return {}; } return config.compiler->defines(languageType, config.parserArguments[languageType]); } QHash CompilerProvider::defines( ProjectBaseItem* item ) const { auto config = configForItem(item); auto languageType = Utils::Cpp; if (item) { languageType = Utils::languageType(item->path().path(), config.parserArguments.parseAmbiguousAsCPP); } // If called on files that we can't compile, return an empty set of defines. if (languageType == Utils::Other) { return {}; } return config.compiler->defines(languageType, config.parserArguments[languageType]); } Path::List CompilerProvider::includes( const QString& path ) const { auto config = configForItem(nullptr); auto languageType = Utils::languageType(path, config.parserArguments.parseAmbiguousAsCPP); // If called on files that we can't compile, return an empty set of includes. if (languageType == Utils::Other) { return {}; } return config.compiler->includes(languageType, config.parserArguments[languageType]); } Path::List CompilerProvider::includes( ProjectBaseItem* item ) const { auto config = configForItem(item); auto languageType = Utils::Cpp; if (item) { languageType = Utils::languageType(item->path().path(), config.parserArguments.parseAmbiguousAsCPP); } // If called on files that we can't compile, return an empty set of includes. if (languageType == Utils::Other) { return {}; } return config.compiler->includes(languageType, config.parserArguments[languageType]); } Path::List CompilerProvider::frameworkDirectories( const QString& /* path */ ) const { return {}; } Path::List CompilerProvider::frameworkDirectories( ProjectBaseItem* /* item */ ) const { return {}; } IDefinesAndIncludesManager::Type CompilerProvider::type() const { return IDefinesAndIncludesManager::CompilerSpecific; } CompilerPointer CompilerProvider::defaultCompiler() const { if (m_defaultProvider) return m_defaultProvider; auto rt = ICore::self()->runtimeController()->currentRuntime(); const auto path = QFile::decodeName(rt->getenv("PATH")).split(QDir::listSeparator()); for ( const CompilerPointer& compiler : m_compilers ) { const bool absolutePath = QDir::isAbsolutePath(compiler->path()); if ((absolutePath && QFileInfo::exists(rt->pathInHost(Path(compiler->path())).toLocalFile())) || QStandardPaths::findExecutable( compiler->path(), path).isEmpty() ) { continue; } m_defaultProvider = compiler; break; } if (!m_defaultProvider) m_defaultProvider = createDummyCompiler(); qCDebug(DEFINESANDINCLUDES) << "new default compiler" << rt->name() << m_defaultProvider->name() << m_defaultProvider->path(); return m_defaultProvider; } QVector< CompilerPointer > CompilerProvider::compilers() const { return m_compilers; } CompilerPointer CompilerProvider::compilerForItem( KDevelop::ProjectBaseItem* item ) const { auto compiler = configForItem(item).compiler; Q_ASSERT(compiler); return compiler; } bool CompilerProvider::registerCompiler(const CompilerPointer& compiler) { if (!compiler) { return false; } for (auto& c : qAsConst(m_compilers)) { if (c->name() == compiler->name()) { return false; } } m_compilers.append(compiler); return true; } void CompilerProvider::unregisterCompiler(const CompilerPointer& compiler) { if (!compiler->editable()) { return; } for (int i = 0; i < m_compilers.count(); i++) { if (m_compilers[i]->name() == compiler->name()) { m_compilers.remove(i); break; } } } QVector< CompilerFactoryPointer > CompilerProvider::compilerFactories() const { return m_factories; } void CompilerProvider::retrieveUserDefinedCompilers() { const auto compilers = m_settings->userDefinedCompilers(); for (auto& c : compilers) { registerCompiler(c); } } diff --git a/plugins/custom-definesandincludes/compilerprovider/compilerprovider.h b/plugins/custom-definesandincludes/compilerprovider/compilerprovider.h index 61ac6c846c..3a229cadac 100644 --- a/plugins/custom-definesandincludes/compilerprovider/compilerprovider.h +++ b/plugins/custom-definesandincludes/compilerprovider/compilerprovider.h @@ -1,80 +1,81 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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 . * */ #ifndef COMPILERSPROVIDER_H #define COMPILERSPROVIDER_H #include "icompilerfactory.h" #include class SettingsManager; class CompilerProvider : public QObject, public KDevelop::IDefinesAndIncludesManager::Provider { Q_OBJECT public: explicit CompilerProvider( SettingsManager* settings, QObject* parent = nullptr ); ~CompilerProvider() override; KDevelop::Defines defines( const QString& path ) const override; KDevelop::Defines defines( KDevelop::ProjectBaseItem* item ) const override; KDevelop::Path::List includes( const QString& path ) const override; KDevelop::Path::List includes( KDevelop::ProjectBaseItem* item ) const override; KDevelop::Path::List frameworkDirectories( const QString& path ) const override; KDevelop::Path::List frameworkDirectories( KDevelop::ProjectBaseItem* item ) const override; KDevelop::IDefinesAndIncludesManager::Type type() const override; /// @return current compiler for the @p item CompilerPointer compilerForItem( KDevelop::ProjectBaseItem* item ) const; /// @return list of all available compilers QVector compilers() const; /** * Adds compiler to the list of available compilers * @return true on success (if there is no compiler with the same name registered). */ bool registerCompiler(const CompilerPointer& compiler); /// Removes compiler from the list of available compilers void unregisterCompiler( const CompilerPointer& compiler ); /// @return All available factories QVector compilerFactories() const; /// @returns a default compiler CompilerPointer defaultCompiler() const; private Q_SLOTS: void retrieveUserDefinedCompilers(); + void projectChanged(KDevelop::IProject* p); private: mutable CompilerPointer m_defaultProvider; QVector m_compilers; QVector m_factories; SettingsManager* m_settings; }; #endif // COMPILERSPROVIDER_H diff --git a/plugins/custom-definesandincludes/compilerprovider/icompilerfactory.h b/plugins/custom-definesandincludes/compilerprovider/icompilerfactory.h index 05155334d8..02c3afaffd 100644 --- a/plugins/custom-definesandincludes/compilerprovider/icompilerfactory.h +++ b/plugins/custom-definesandincludes/compilerprovider/icompilerfactory.h @@ -1,52 +1,55 @@ /* * This file is part of KDevelop * * Copyright 2014 Sergey Kalinichev * * 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 . * */ #ifndef ICOMPILERFACTORY_H #define ICOMPILERFACTORY_H #include "icompiler.h" class CompilerProvider; /// Interface that represents a factory for creating compilers class ICompilerFactory { public: virtual QString name() const = 0; ///@return new compiler ///@see ICompiler virtual CompilerPointer createCompiler( const QString& name, const QString& path, bool editable = true ) const = 0; /** * registers default compilers for the @p provider * E.g. for gcc default compilers could be "gcc c99" and "gcc c++11" */ virtual void registerDefaultCompilers(CompilerProvider* provider) const = 0; + /** @returns whether @p path is a compiler supported by the factory */ + virtual bool isSupported(const KDevelop::Path &path) const = 0; + virtual ~ICompilerFactory() = default; }; using CompilerFactoryPointer = QSharedPointer; #endif // ICOMPILERFACTORY_H diff --git a/plugins/custommake/custommakemanager.cpp b/plugins/custommake/custommakemanager.cpp index a2fe2a184b..7eeed57a0b 100644 --- a/plugins/custommake/custommakemanager.cpp +++ b/plugins/custommake/custommakemanager.cpp @@ -1,328 +1,332 @@ /* KDevelop Custom Makefile Support * * Copyright 2007 Dukju Ahn * Copyright 2011 Milian Wolff * * 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. */ #include "custommakemanager.h" #include "custommakemodelitems.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; class CustomMakeProvider : public IDefinesAndIncludesManager::BackgroundProvider { public: explicit CustomMakeProvider(CustomMakeManager* manager) : m_customMakeManager(manager) , m_resolver(new MakeFileResolver()) {} // NOTE: Fixes build failures for GCC versions <4.8. // cf. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53613 ~CustomMakeProvider() Q_DECL_NOEXCEPT override; QHash< QString, QString > definesInBackground(const QString&) const override { return {}; } Path::List resolvePathInBackground(const QString& path, const bool isFrameworks) const { { QReadLocker lock(&m_lock); bool inProject = std::any_of(m_customMakeManager->m_projectPaths.constBegin(), m_customMakeManager->m_projectPaths.constEnd(), [&path](const QString& projectPath) { return path.startsWith(projectPath); } ); if (!inProject) { return {}; } } if (isFrameworks) { return m_resolver->resolveIncludePath(path).frameworkDirectories; } else { return m_resolver->resolveIncludePath(path).paths; } } Path::List includesInBackground(const QString& path) const override { return resolvePathInBackground(path, false); } Path::List frameworkDirectoriesInBackground(const QString& path) const override { return resolvePathInBackground(path, true); } IDefinesAndIncludesManager::Type type() const override { return IDefinesAndIncludesManager::ProjectSpecific; } CustomMakeManager* m_customMakeManager; QScopedPointer m_resolver; mutable QReadWriteLock m_lock; }; // NOTE: Fixes build failures for GCC versions <4.8. // See above. CustomMakeProvider::~CustomMakeProvider() Q_DECL_NOEXCEPT {} K_PLUGIN_FACTORY_WITH_JSON(CustomMakeSupportFactory, "kdevcustommakemanager.json", registerPlugin(); ) CustomMakeManager::CustomMakeManager( QObject *parent, const QVariantList& args ) : KDevelop::AbstractFileManagerPlugin( QStringLiteral("kdevcustommakemanager"), parent ) , m_provider(new CustomMakeProvider(this)) { Q_UNUSED(args) setXMLFile( QStringLiteral("kdevcustommakemanager.rc") ); // TODO use CustomMakeBuilder IPlugin* i = core()->pluginController()->pluginForExtension( QStringLiteral("org.kdevelop.IMakeBuilder") ); Q_ASSERT(i); m_builder = i->extension(); Q_ASSERT(m_builder); connect(this, &CustomMakeManager::reloadedFileItem, this, &CustomMakeManager::reloadMakefile); connect(ICore::self()->projectController(), &IProjectController::projectClosing, this, &CustomMakeManager::projectClosing); IDefinesAndIncludesManager::manager()->registerBackgroundProvider(m_provider.data()); } CustomMakeManager::~CustomMakeManager() { } IProjectBuilder* CustomMakeManager::builder() const { Q_ASSERT(m_builder); return m_builder; } Path::List CustomMakeManager::includeDirectories(KDevelop::ProjectBaseItem*) const { return Path::List(); } Path::List CustomMakeManager::frameworkDirectories(KDevelop::ProjectBaseItem*) const { return Path::List(); } QHash CustomMakeManager::defines(KDevelop::ProjectBaseItem*) const { return QHash(); } QString CustomMakeManager::extraArguments(KDevelop::ProjectBaseItem*) const { return {}; } ProjectTargetItem* CustomMakeManager::createTarget(const QString& target, KDevelop::ProjectFolderItem *parent) { Q_UNUSED(target) Q_UNUSED(parent) return nullptr; } bool CustomMakeManager::addFilesToTarget(const QList< ProjectFileItem* > &files, ProjectTargetItem* parent) { Q_UNUSED( files ) Q_UNUSED( parent ) return false; } bool CustomMakeManager::removeTarget(KDevelop::ProjectTargetItem *target) { Q_UNUSED( target ) return false; } bool CustomMakeManager::removeFilesFromTargets(const QList< ProjectFileItem* > &targetFiles) { Q_UNUSED( targetFiles ) return false; } bool CustomMakeManager::hasBuildInfo(KDevelop::ProjectBaseItem* item) const { Q_UNUSED(item); return false; } Path CustomMakeManager::buildDirectory(KDevelop::ProjectBaseItem* item) const { auto *fi=dynamic_cast(item); for(; !fi && item; ) { item=item->parent(); fi=dynamic_cast(item); } if(!fi) { return item->project()->path(); } return fi->path(); } QList CustomMakeManager::targets(KDevelop::ProjectFolderItem*) const { QList ret; return ret; } static bool isMakefile(const QString& fileName) { return ( fileName == QLatin1String("Makefile") || fileName == QLatin1String("makefile") || fileName == QLatin1String("GNUmakefile") || fileName == QLatin1String("BSDmakefile") ); } void CustomMakeManager::createTargetItems(IProject* project, const Path& path, ProjectBaseItem* parent) { Q_ASSERT(isMakefile(path.lastPathSegment())); foreach(const QString& target, parseCustomMakeFile( path )) { if (!isValid(Path(parent->path(), target), false, project)) { continue; } new CustomMakeTargetItem( project, target, parent ); } } ProjectFileItem* CustomMakeManager::createFileItem(IProject* project, const Path& path, ProjectBaseItem* parent) { auto* item = new ProjectFileItem(project, path, parent); if (isMakefile(path.lastPathSegment())){ createTargetItems(project, path, parent); } return item; } void CustomMakeManager::reloadMakefile(ProjectFileItem* file) { if( !isMakefile(file->path().lastPathSegment())){ return; } ProjectBaseItem* parent = file->parent(); // remove the items that are Makefile targets foreach(ProjectBaseItem* item, parent->children()){ if (item->target()){ delete item; } } // Recreate the targets. createTargetItems(parent->project(), file->path(), parent); } ProjectFolderItem* CustomMakeManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { // TODO more faster algorithm. should determine whether this directory // contains makefile or not. return new KDevelop::ProjectBuildFolderItem( project, path, parent ); } KDevelop::ProjectFolderItem* CustomMakeManager::import(KDevelop::IProject *project) { if( project->path().isRemote() ) { //FIXME turn this into a real warning qCWarning(CUSTOMMAKE) << project->path() << "not a local file. Custom make support doesn't handle remote projects"; return nullptr; } { QWriteLocker lock(&m_provider->m_lock); m_projectPaths.insert(project->path().path()); } return AbstractFileManagerPlugin::import( project ); } ///////////////////////////////////////////////////////////////////////////// // private slots ///TODO: move to background thread, probably best would be to use a proper ParseJob QStringList CustomMakeManager::parseCustomMakeFile( const Path &makefile ) { if( !makefile.isValid() ) return QStringList(); QStringList ret; // the list of targets QFile f( makefile.toLocalFile() ); if ( !f.open( QIODevice::ReadOnly | QIODevice::Text ) ) { qCDebug(CUSTOMMAKE) << "could not open" << makefile; return ret; } QRegExp targetRe(QStringLiteral("^ *([^\\t$.#]\\S+) *:?:(?!=).*$")); targetRe.setMinimal( true ); QString str; QTextStream stream( &f ); while ( !stream.atEnd() ) { str = stream.readLine(); if ( targetRe.indexIn( str ) != -1 ) { QString tmpTarget = targetRe.cap( 1 ).simplified(); if ( ! ret.contains( tmpTarget ) ) ret.append( tmpTarget ); } } f.close(); return ret; } void CustomMakeManager::projectClosing(IProject* project) { QWriteLocker lock(&m_provider->m_lock); m_projectPaths.remove(project->path().path()); } void CustomMakeManager::unload() { IDefinesAndIncludesManager::manager()->unregisterBackgroundProvider(m_provider.data()); } -#include "custommakemanager.moc" +KDevelop::Path CustomMakeManager::compiler(KDevelop::ProjectTargetItem* item) const +{ + return {}; +} +#include "custommakemanager.moc" diff --git a/plugins/custommake/custommakemanager.h b/plugins/custommake/custommakemanager.h index 956a1910fc..ca1df49c66 100644 --- a/plugins/custommake/custommakemanager.h +++ b/plugins/custommake/custommakemanager.h @@ -1,146 +1,148 @@ /* KDevelop Custom Makefile Support * * Copyright 2007 Dukju Ahn * Copyright 2011 Milian Wolff * * 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. */ #ifndef CUSTOMMAKEMANAGER_H #define CUSTOMMAKEMANAGER_H #include #include #include #include class IMakeBuilder; class CustomMakeProvider; class CustomMakeManager : public KDevelop::AbstractFileManagerPlugin, public KDevelop::IBuildSystemManager { Q_OBJECT Q_INTERFACES( KDevelop::IBuildSystemManager ) public: explicit CustomMakeManager( QObject *parent = nullptr, const QVariantList& args = QVariantList() ); ~CustomMakeManager() override; Features features() const override { return Features(Folders | Targets | Files); } KDevelop::ProjectFolderItem* import(KDevelop::IProject* project) override; /** * Provide access to the builder */ KDevelop::IProjectBuilder* builder() const override; /** * Provide a list of include directories. */ KDevelop::Path::List includeDirectories(KDevelop::ProjectBaseItem*) const override; /** * Provide a list of framework directories. */ KDevelop::Path::List frameworkDirectories(KDevelop::ProjectBaseItem*) const override; /** * Provide a list of files that contain the preprocessor defines for the * project */ QHash defines(KDevelop::ProjectBaseItem*) const override; QString extraArguments(KDevelop::ProjectBaseItem *item) const override; /** * Create a new target * * Creates the target specified by @p target to the folder @p parent and * modifies the underlying build system if needed */ KDevelop::ProjectTargetItem* createTarget(const QString& target, KDevelop::ProjectFolderItem *parent) override; /** * Add a file to a target * * Adds the file specified by @p file to the target @p parent and modifies * the underlying build system if needed. */ bool addFilesToTarget(const QList &files, KDevelop::ProjectTargetItem *parent) override; /** * Remove a target * * Removes the target specified by @p target and * modifies the underlying build system if needed. */ bool removeTarget(KDevelop::ProjectTargetItem *target) override; /** * Remove a file from a target * * Removes the file specified by @p file from the folder @p parent and * modifies the underlying build system if needed. The file is not removed * from the folder it is in */ bool removeFilesFromTargets(const QList&) override; /** * Test if @p item has any includes or defines from this BSM */ bool hasBuildInfo(KDevelop::ProjectBaseItem* item) const override; /** * Get the toplevel build directory for the project */ KDevelop::Path buildDirectory(KDevelop::ProjectBaseItem*) const override; /** * Get a list of all the targets in this project * * The list returned by this function should be checked to verify it is not * empty before using it * * @return The list of targets for this project * @todo implement */ QList targets(KDevelop::ProjectFolderItem*) const override; + KDevelop::Path compiler(KDevelop::ProjectTargetItem * p) const override; + protected: KDevelop::ProjectFileItem* createFileItem(KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent) override; KDevelop::ProjectFolderItem* createFolderItem(KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent = nullptr) override; void unload() override; private Q_SLOTS: void reloadMakefile(KDevelop::ProjectFileItem *item); void projectClosing(KDevelop::IProject*); private: /** * Initialize targets by reading Makefile in @arg dir * @return Target lists in Makefile at @arg dir. */ QStringList parseCustomMakeFile( const KDevelop::Path &makefile ); void createTargetItems(KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent); private: IMakeBuilder *m_builder = nullptr; QScopedPointer m_provider; QSet m_projectPaths; friend class CustomMakeProvider; }; #endif diff --git a/plugins/qmakemanager/qmakemanager.cpp b/plugins/qmakemanager/qmakemanager.cpp index 83aa0556ee..bc3eff38f0 100644 --- a/plugins/qmakemanager/qmakemanager.cpp +++ b/plugins/qmakemanager/qmakemanager.cpp @@ -1,513 +1,518 @@ /* KDevelop QMake Support * * Copyright 2006 Andreas Pakulat * * 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 "qmakemanager.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "qmakemodelitems.h" #include "qmakeprojectfile.h" #include "qmakecache.h" #include "qmakemkspecs.h" #include "qmakejob.h" #include "qmakebuilddirchooserdialog.h" #include "qmakeconfig.h" #include "qmakeutils.h" #include using namespace KDevelop; // BEGIN Helpers QMakeFolderItem* findQMakeFolderParent(ProjectBaseItem* item) { QMakeFolderItem* p = nullptr; while (!p && item) { p = dynamic_cast(item); item = item->parent(); } return p; } // END Helpers K_PLUGIN_FACTORY_WITH_JSON(QMakeSupportFactory, "kdevqmakemanager.json", registerPlugin();) QMakeProjectManager::QMakeProjectManager(QObject* parent, const QVariantList&) : AbstractFileManagerPlugin(QStringLiteral("kdevqmakemanager"), parent) , IBuildSystemManager() { IPlugin* i = core()->pluginController()->pluginForExtension(QStringLiteral("org.kdevelop.IQMakeBuilder")); Q_ASSERT(i); m_builder = i->extension(); Q_ASSERT(m_builder); connect(this, SIGNAL(folderAdded(KDevelop::ProjectFolderItem*)), this, SLOT(slotFolderAdded(KDevelop::ProjectFolderItem*))); m_runQMake = new QAction(QIcon::fromTheme(QStringLiteral("qtlogo")), i18n("Run QMake"), this); connect(m_runQMake, &QAction::triggered, this, &QMakeProjectManager::slotRunQMake); } QMakeProjectManager::~QMakeProjectManager() { } IProjectFileManager::Features QMakeProjectManager::features() const { return Features(Folders | Targets | Files); } bool QMakeProjectManager::isValid(const Path& path, const bool isFolder, IProject* project) const { if (!isFolder && path.lastPathSegment().startsWith(QLatin1String("Makefile"))) { return false; } return AbstractFileManagerPlugin::isValid(path, isFolder, project); } Path QMakeProjectManager::buildDirectory(ProjectBaseItem* item) const { /// TODO: support includes by some other parent or sibling in a different file-tree-branch QMakeFolderItem* qmakeItem = findQMakeFolderParent(item); Path dir; if (qmakeItem) { if (!qmakeItem->parent()) { // build root item dir = QMakeConfig::buildDirFromSrc(qmakeItem->project(), qmakeItem->path()); } else { // build sub-item foreach (QMakeProjectFile* pro, qmakeItem->projectFiles()) { if (QDir(pro->absoluteDir()) == QFileInfo(qmakeItem->path().toUrl().toLocalFile() + QLatin1Char('/')).absoluteDir() || pro->hasSubProject(qmakeItem->path().toUrl().toLocalFile())) { // get path from project root and it to buildDir dir = QMakeConfig::buildDirFromSrc(qmakeItem->project(), Path(pro->absoluteDir())); break; } } } } qCDebug(KDEV_QMAKE) << "build dir for" << item->text() << item->path() << "is:" << dir; return dir; } ProjectFolderItem* QMakeProjectManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { if (!parent) { return projectRootItem(project, path); } else if (ProjectFolderItem* buildFolder = buildFolderItem(project, path, parent)) { // child folder in a qmake folder return buildFolder; } else { return AbstractFileManagerPlugin::createFolderItem(project, path, parent); } } ProjectFolderItem* QMakeProjectManager::projectRootItem(IProject* project, const Path& path) { QDir dir(path.toLocalFile()); auto item = new QMakeFolderItem(project, path); QHash qmvars = QMakeUtils::queryQMake(project); const QString mkSpecFile = QMakeConfig::findBasicMkSpec(qmvars); Q_ASSERT(!mkSpecFile.isEmpty()); QMakeMkSpecs* mkspecs = new QMakeMkSpecs(mkSpecFile, qmvars); mkspecs->setProject(project); mkspecs->read(); QMakeCache* cache = findQMakeCache(project); if (cache) { cache->setMkSpecs(mkspecs); cache->read(); } QStringList projectfiles = dir.entryList(QStringList() << QStringLiteral("*.pro")); for (const auto& projectfile : projectfiles) { Path proPath(path, projectfile); /// TODO: use Path in QMakeProjectFile QMakeProjectFile* scope = new QMakeProjectFile(proPath.toLocalFile()); scope->setProject(project); scope->setMkSpecs(mkspecs); if (cache) { scope->setQMakeCache(cache); } scope->read(); qCDebug(KDEV_QMAKE) << "top-level scope with variables:" << scope->variables(); item->addProjectFile(scope); } return item; } ProjectFolderItem* QMakeProjectManager::buildFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent) { // find .pro or .pri files in dir QDir dir(path.toLocalFile()); const QStringList projectFiles = dir.entryList(QStringList{QStringLiteral("*.pro"), QStringLiteral("*.pri")}, QDir::Files); if (projectFiles.isEmpty()) { return nullptr; } auto folderItem = new QMakeFolderItem(project, path, parent); // TODO: included by not-parent file (in a nother file-tree-branch). QMakeFolderItem* qmakeParent = findQMakeFolderParent(parent); if (!qmakeParent) { // happens for bad qmake configurations return nullptr; } for (const QString& file : projectFiles) { const QString absFile = dir.absoluteFilePath(file); // TODO: multiple includes by different .pro's QMakeProjectFile* parentPro = nullptr; foreach (QMakeProjectFile* p, qmakeParent->projectFiles()) { if (p->hasSubProject(absFile)) { parentPro = p; break; } } if (!parentPro && file.endsWith(QLatin1String(".pri"))) { continue; } qCDebug(KDEV_QMAKE) << "add project file:" << absFile; if (parentPro) { qCDebug(KDEV_QMAKE) << "parent:" << parentPro->absoluteFile(); } else { qCDebug(KDEV_QMAKE) << "no parent, assume project root"; } auto qmscope = new QMakeProjectFile(absFile); qmscope->setProject(project); const QFileInfo info(absFile); const QDir d = info.dir(); /// TODO: cleanup if (parentPro) { // subdir if (QMakeCache* cache = findQMakeCache(project, Path(d.canonicalPath()))) { cache->setMkSpecs(parentPro->mkSpecs()); cache->read(); qmscope->setQMakeCache(cache); } else { qmscope->setQMakeCache(parentPro->qmakeCache()); } qmscope->setMkSpecs(parentPro->mkSpecs()); } else { // new project auto* root = dynamic_cast(project->projectItem()); Q_ASSERT(root); qmscope->setMkSpecs(root->projectFiles().first()->mkSpecs()); if (root->projectFiles().first()->qmakeCache()) { qmscope->setQMakeCache(root->projectFiles().first()->qmakeCache()); } } if (qmscope->read()) { // TODO: only on read? folderItem->addProjectFile(qmscope); } else { delete qmscope; return nullptr; } } return folderItem; } void QMakeProjectManager::slotFolderAdded(ProjectFolderItem* folder) { auto* qmakeParent = dynamic_cast(folder); if (!qmakeParent) { return; } qCDebug(KDEV_QMAKE) << "adding targets for" << folder->path(); foreach (QMakeProjectFile* pro, qmakeParent->projectFiles()) { foreach (const QString& s, pro->targets()) { if (!isValid(Path(folder->path(), s), false, folder->project())) { continue; } qCDebug(KDEV_QMAKE) << "adding target:" << s; Q_ASSERT(!s.isEmpty()); auto target = new QMakeTargetItem(pro, folder->project(), s, folder); foreach (const QString& path, pro->filesForTarget(s)) { new ProjectFileItem(folder->project(), Path(path), target); /// TODO: signal? } } } } ProjectFolderItem* QMakeProjectManager::import(IProject* project) { const Path dirName = project->path(); if (dirName.isRemote()) { // FIXME turn this into a real warning qCWarning(KDEV_QMAKE) << "not a local file. QMake support doesn't handle remote projects"; return nullptr; } QMakeUtils::checkForNeedingConfigure(project); ProjectFolderItem* ret = AbstractFileManagerPlugin::import(project); connect(projectWatcher(project), &KDirWatch::dirty, this, &QMakeProjectManager::slotDirty); return ret; } void QMakeProjectManager::slotDirty(const QString& path) { if (!path.endsWith(QLatin1String(".pro")) && !path.endsWith(QLatin1String(".pri"))) { return; } QFileInfo info(path); if (!info.isFile()) { return; } const QUrl url = QUrl::fromLocalFile(path); if (!isValid(Path(url), false, nullptr)) { return; } IProject* project = ICore::self()->projectController()->findProjectForUrl(url); if (!project) { // this can happen when we create/remove lots of files in a // sub dir of a project - ignore such cases for now return; } bool finished = false; foreach (ProjectFolderItem* folder, project->foldersForPath(IndexedString(KIO::upUrl(url)))) { if (auto* qmakeFolder = dynamic_cast(folder)) { foreach (QMakeProjectFile* pro, qmakeFolder->projectFiles()) { if (pro->absoluteFile() == path) { // TODO: children // TODO: cache added qCDebug(KDEV_QMAKE) << "reloading" << pro << path; pro->read(); } } finished = true; } else if (ProjectFolderItem* newFolder = buildFolderItem(project, folder->path(), folder->parent())) { qCDebug(KDEV_QMAKE) << "changing from normal folder to qmake project folder:" << folder->path().toUrl(); // .pro / .pri file did not exist before while (folder->rowCount()) { newFolder->appendRow(folder->takeRow(0)); } folder->parent()->removeRow(folder->row()); folder = newFolder; finished = true; } if (finished) { // remove existing targets and readd them for (int i = 0; i < folder->rowCount(); ++i) { if (folder->child(i)->target()) { folder->removeRow(i); } } /// TODO: put into it's own function once we add more stuff to that slot slotFolderAdded(folder); break; } } } QList QMakeProjectManager::targets(ProjectFolderItem* item) const { Q_UNUSED(item) return QList(); } IProjectBuilder* QMakeProjectManager::builder() const { Q_ASSERT(m_builder); return m_builder; } Path::List QMakeProjectManager::collectDirectories(ProjectBaseItem* item, const bool collectIncludes) const { Path::List list; QMakeFolderItem* folder = findQMakeFolderParent(item); if (folder) { foreach (QMakeProjectFile* pro, folder->projectFiles()) { if (pro->files().contains(item->path().toLocalFile())) { const QStringList directories = collectIncludes ? pro->includeDirectories() : pro->frameworkDirectories(); for (const QString& dir : directories) { Path path(dir); if (!list.contains(path)) { list << path; } } } } if (list.isEmpty()) { // fallback for new files, use all possible include dirs foreach (QMakeProjectFile* pro, folder->projectFiles()) { const QStringList directories = collectIncludes ? pro->includeDirectories() : pro->frameworkDirectories(); for (const QString& dir : directories) { Path path(dir); if (!list.contains(path)) { list << path; } } } } // make sure the base dir is included if (!list.contains(folder->path())) { list << folder->path(); } // qCDebug(KDEV_QMAKE) << "include dirs for" << item->path() << ":" << list; } return list; } Path::List QMakeProjectManager::includeDirectories(ProjectBaseItem* item) const { return collectDirectories(item); } Path::List QMakeProjectManager::frameworkDirectories(ProjectBaseItem* item) const { return collectDirectories(item, false); } QHash QMakeProjectManager::defines(ProjectBaseItem* item) const { QHash d; QMakeFolderItem* folder = findQMakeFolderParent(item); if (!folder) { // happens for bad qmake configurations return d; } foreach (QMakeProjectFile* pro, folder->projectFiles()) { foreach (QMakeProjectFile::DefinePair def, pro->defines()) { d.insert(def.first, def.second); } } return d; } QString QMakeProjectManager::extraArguments(KDevelop::ProjectBaseItem *item) const { QMakeFolderItem* folder = findQMakeFolderParent(item); if (!folder) { // happens for bad qmake configurations return {}; } QStringList d; foreach (QMakeProjectFile* pro, folder->projectFiles()) { d << pro->extraArguments(); } return d.join(QLatin1Char(' ')); } bool QMakeProjectManager::hasBuildInfo(KDevelop::ProjectBaseItem* item) const { return findQMakeFolderParent(item); } QMakeCache* QMakeProjectManager::findQMakeCache(IProject* project, const Path& path) const { QDir curdir(QMakeConfig::buildDirFromSrc(project, !path.isValid() ? project->path() : path).toLocalFile()); curdir.makeAbsolute(); while (!curdir.exists(QStringLiteral(".qmake.cache")) && !curdir.isRoot() && curdir.cdUp()) { qCDebug(KDEV_QMAKE) << curdir; } if (curdir.exists(QStringLiteral(".qmake.cache"))) { qCDebug(KDEV_QMAKE) << "Found QMake cache in " << curdir.absolutePath(); return new QMakeCache(curdir.canonicalPath() + QLatin1String("/.qmake.cache")); } return nullptr; } ContextMenuExtension QMakeProjectManager::contextMenuExtension(Context* context, QWidget* parent) { Q_UNUSED(parent); ContextMenuExtension ext; if (context->hasType(Context::ProjectItemContext)) { auto* pic = dynamic_cast(context); Q_ASSERT(pic); if (pic->items().isEmpty()) { return ext; } m_actionItem = dynamic_cast(pic->items().first()); if (m_actionItem) { ext.addAction(ContextMenuExtension::ProjectGroup, m_runQMake); } } return ext; } void QMakeProjectManager::slotRunQMake() { Q_ASSERT(m_actionItem); Path srcDir = m_actionItem->path(); Path buildDir = QMakeConfig::buildDirFromSrc(m_actionItem->project(), srcDir); QMakeJob* job = new QMakeJob(srcDir.toLocalFile(), buildDir.toLocalFile(), this); job->setQMakePath(QMakeConfig::qmakeExecutable(m_actionItem->project())); KConfigGroup cg(m_actionItem->project()->projectConfiguration(), QMakeConfig::CONFIG_GROUP); QString installPrefix = cg.readEntry(QMakeConfig::INSTALL_PREFIX, QString()); if (!installPrefix.isEmpty()) job->setInstallPrefix(installPrefix); job->setBuildType(cg.readEntry(QMakeConfig::BUILD_TYPE, 0)); job->setExtraArguments(cg.readEntry(QMakeConfig::EXTRA_ARGUMENTS, QString())); KDevelop::ICore::self()->runController()->registerJob(job); } +KDevelop::Path QMakeProjectManager::compiler(KDevelop::ProjectTargetItem* p) const +{ + return {}; +} + #include "qmakemanager.moc" diff --git a/plugins/qmakemanager/qmakemanager.h b/plugins/qmakemanager/qmakemanager.h index 53bf3e4843..0a6860b6bf 100644 --- a/plugins/qmakemanager/qmakemanager.h +++ b/plugins/qmakemanager/qmakemanager.h @@ -1,105 +1,107 @@ /* KDevelop QMake Support * * Copyright 2006 Andreas Pakulat * Copyright 2010 Milian Wolff * * 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 QMAKEMANAGER_H #define QMAKEMANAGER_H #include #include class QMakeFolderItem; class IQMakeBuilder; class QMakeCache; class QMakeProjectFile; class QMakeProjectManager : public KDevelop::AbstractFileManagerPlugin, public KDevelop::IBuildSystemManager { Q_OBJECT Q_INTERFACES( KDevelop::IBuildSystemManager ) public: explicit QMakeProjectManager( QObject* parent = nullptr, const QVariantList& args = QVariantList() ); ~QMakeProjectManager() override; KDevelop::ContextMenuExtension contextMenuExtension(KDevelop::Context* context, QWidget* parent) override; //BEGIN AbstractFileManager KDevelop::ProjectFolderItem* import( KDevelop::IProject* project ) override; KDevelop::ProjectFolderItem* createFolderItem( KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent = nullptr ) override; Features features() const override; bool isValid( const KDevelop::Path& path, const bool isFolder, KDevelop::IProject* project ) const override; //END AbstractFileManager //BEGIN IBuildSystemManager //TODO KDevelop::IProjectBuilder* builder() const override; KDevelop::Path buildDirectory(KDevelop::ProjectBaseItem*) const override; KDevelop::Path::List collectDirectories(KDevelop::ProjectBaseItem*, const bool collectIncludes=true) const; KDevelop::Path::List includeDirectories(KDevelop::ProjectBaseItem*) const override; KDevelop::Path::List frameworkDirectories(KDevelop::ProjectBaseItem* item) const override; QHash defines(KDevelop::ProjectBaseItem*) const override; QString extraArguments(KDevelop::ProjectBaseItem *item) const override; bool hasBuildInfo(KDevelop::ProjectBaseItem*) const override; KDevelop::ProjectTargetItem* createTarget( const QString&, KDevelop::ProjectFolderItem* ) override { return nullptr; } bool addFilesToTarget(const QList&, KDevelop::ProjectTargetItem*) override { return false; } bool removeTarget( KDevelop::ProjectTargetItem* ) override { return false; } bool removeFilesFromTargets(const QList&) override { return false; } QList targets(KDevelop::ProjectFolderItem*) const override; + + KDevelop::Path compiler(KDevelop::ProjectTargetItem* item) const override; //END IBuildSystemManager private Q_SLOTS: void slotFolderAdded( KDevelop::ProjectFolderItem* folder ); void slotRunQMake(); void slotDirty(const QString& path); private: KDevelop::ProjectFolderItem* projectRootItem( KDevelop::IProject* project, const KDevelop::Path& path ); KDevelop::ProjectFolderItem* buildFolderItem( KDevelop::IProject* project, const KDevelop::Path& path, KDevelop::ProjectBaseItem* parent ); QMakeCache* findQMakeCache( KDevelop::IProject* project, const KDevelop::Path &path = {} ) const; IQMakeBuilder* m_builder = nullptr; mutable QString m_qtIncludeDir; QAction* m_runQMake = nullptr; QMakeFolderItem* m_actionItem; }; #endif