File indexing completed on 2024-04-28 04:38:29

0001 /*
0002     SPDX-FileCopyrightText: 2007 Dukju Ahn <dukjuahn@gmail.com>
0003     SPDX-FileCopyrightText: 2011 Milian Wolff <mail@milianw.de>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "custommakemanager.h"
0009 #include "custommakemodelitems.h"
0010 #include <debug.h>
0011 #include <interfaces/icore.h>
0012 #include <interfaces/iproject.h>
0013 #include <interfaces/iprojectcontroller.h>
0014 #include <interfaces/iplugincontroller.h>
0015 #include <makebuilder/imakebuilder.h>
0016 #include <project/projectmodel.h>
0017 #include <project/helper.h>
0018 #include <custom-definesandincludes/idefinesandincludesmanager.h>
0019 #include <makefileresolver/makefileresolver.h>
0020 
0021 #include <KPluginFactory>
0022 #include <KLocalizedString>
0023 
0024 #include <QFile>
0025 #include <QReadWriteLock>
0026 #include <QReadLocker>
0027 #include <QWriteLocker>
0028 
0029 
0030 #include <algorithm>
0031 
0032 using namespace KDevelop;
0033 
0034 class CustomMakeProvider : public IDefinesAndIncludesManager::BackgroundProvider
0035 {
0036 public:
0037     explicit CustomMakeProvider(CustomMakeManager* manager)
0038         : m_customMakeManager(manager)
0039         , m_resolver(new MakeFileResolver())
0040     {}
0041 
0042     // NOTE: Fixes build failures for GCC versions <4.8.
0043     // cf. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53613
0044     ~CustomMakeProvider() Q_DECL_NOEXCEPT override;
0045 
0046     QHash< QString, QString > definesInBackground(const QString&) const override
0047     {
0048         return {};
0049     }
0050 
0051     Path::List resolvePathInBackground(const QString& path, const bool isFrameworks) const
0052     {
0053         {
0054             QReadLocker lock(&m_lock);
0055 
0056             bool inProject = std::any_of(m_customMakeManager->m_projectPaths.constBegin(), m_customMakeManager->m_projectPaths.constEnd(), [&path](const QString& projectPath)
0057             {
0058                 return path.startsWith(projectPath);
0059             } );
0060 
0061             if (!inProject) {
0062                 return {};
0063             }
0064         }
0065 
0066         if (isFrameworks) {
0067             return m_resolver->resolveIncludePath(path).frameworkDirectories;
0068         } else {
0069             return m_resolver->resolveIncludePath(path).paths;
0070         }
0071     }
0072 
0073     Path::List includesInBackground(const QString& path) const override
0074     {
0075         return resolvePathInBackground(path, false);
0076     }
0077 
0078     Path::List frameworkDirectoriesInBackground(const QString& path) const override
0079     {
0080         return resolvePathInBackground(path, true);
0081     }
0082 
0083     QString parserArgumentsInBackground(const QString& /*path*/) const override
0084     {
0085         return {};
0086     }
0087 
0088     IDefinesAndIncludesManager::Type type() const override
0089     {
0090         return IDefinesAndIncludesManager::ProjectSpecific;
0091     }
0092 
0093     CustomMakeManager* m_customMakeManager;
0094     QScopedPointer<MakeFileResolver> m_resolver;
0095     mutable QReadWriteLock m_lock;
0096 };
0097 
0098 // NOTE: Fixes build failures for GCC versions <4.8.
0099 // See above.
0100 CustomMakeProvider::~CustomMakeProvider() Q_DECL_NOEXCEPT
0101 {}
0102 
0103 K_PLUGIN_FACTORY_WITH_JSON(CustomMakeSupportFactory, "kdevcustommakemanager.json", registerPlugin<CustomMakeManager>(); )
0104 
0105 CustomMakeManager::CustomMakeManager( QObject *parent, const QVariantList& args )
0106     : KDevelop::AbstractFileManagerPlugin( QStringLiteral("kdevcustommakemanager"), parent )
0107     , m_provider(new CustomMakeProvider(this))
0108 {
0109     Q_UNUSED(args)
0110 
0111     setXMLFile( QStringLiteral("kdevcustommakemanager.rc") );
0112 
0113     // TODO use CustomMakeBuilder
0114     IPlugin* i = core()->pluginController()->pluginForExtension( QStringLiteral("org.kdevelop.IMakeBuilder") );
0115     Q_ASSERT(i);
0116     m_builder = i->extension<IMakeBuilder>();
0117     Q_ASSERT(m_builder);
0118 
0119     connect(this, &CustomMakeManager::reloadedFileItem,
0120             this, &CustomMakeManager::reloadMakefile);
0121 
0122     connect(ICore::self()->projectController(), &IProjectController::projectClosing,
0123             this, &CustomMakeManager::projectClosing);
0124 
0125 
0126     IDefinesAndIncludesManager::manager()->registerBackgroundProvider(m_provider.data());
0127 }
0128 
0129 CustomMakeManager::~CustomMakeManager()
0130 {
0131 }
0132 
0133 IProjectBuilder* CustomMakeManager::builder() const
0134 {
0135     Q_ASSERT(m_builder);
0136     return m_builder;
0137 }
0138 
0139 Path::List CustomMakeManager::includeDirectories(KDevelop::ProjectBaseItem*) const
0140 {
0141     return Path::List();
0142 }
0143 
0144 Path::List CustomMakeManager::frameworkDirectories(KDevelop::ProjectBaseItem*) const
0145 {
0146     return Path::List();
0147 }
0148 
0149 QHash<QString,QString> CustomMakeManager::defines(KDevelop::ProjectBaseItem*) const
0150 {
0151     return QHash<QString,QString>();
0152 }
0153 
0154 QString CustomMakeManager::extraArguments(KDevelop::ProjectBaseItem*) const
0155 {
0156     return {};
0157 }
0158 
0159 ProjectTargetItem* CustomMakeManager::createTarget(const QString& target, KDevelop::ProjectFolderItem *parent)
0160 {
0161     Q_UNUSED(target)
0162     Q_UNUSED(parent)
0163     return nullptr;
0164 }
0165 
0166 bool CustomMakeManager::addFilesToTarget(const QList< ProjectFileItem* > &files, ProjectTargetItem* parent)
0167 {
0168     Q_UNUSED( files )
0169     Q_UNUSED( parent )
0170     return false;
0171 }
0172 
0173 bool CustomMakeManager::removeTarget(KDevelop::ProjectTargetItem *target)
0174 {
0175     Q_UNUSED( target )
0176     return false;
0177 }
0178 
0179 bool CustomMakeManager::removeFilesFromTargets(const QList< ProjectFileItem* > &targetFiles)
0180 {
0181     Q_UNUSED( targetFiles )
0182     return false;
0183 }
0184 
0185 bool CustomMakeManager::hasBuildInfo(KDevelop::ProjectBaseItem* item) const
0186 {
0187     Q_UNUSED(item);
0188     return false;
0189 }
0190 
0191 Path CustomMakeManager::buildDirectory(KDevelop::ProjectBaseItem* item) const
0192 {
0193     auto *fi=dynamic_cast<ProjectFolderItem*>(item);
0194     for(; !fi && item; )
0195     {
0196         item=item->parent();
0197         fi=dynamic_cast<ProjectFolderItem*>(item);
0198     }
0199     if(!fi) {
0200         return item->project()->path();
0201     }
0202     return fi->path();
0203 }
0204 
0205 QList<ProjectTargetItem*> CustomMakeManager::targets(KDevelop::ProjectFolderItem*) const
0206 {
0207     QList<ProjectTargetItem*> ret;
0208     return ret;
0209 }
0210 
0211 static bool isMakefile(const QString& fileName)
0212 {
0213     return  ( fileName == QLatin1String("Makefile")
0214         || fileName == QLatin1String("makefile")
0215         || fileName == QLatin1String("GNUmakefile")
0216         || fileName == QLatin1String("BSDmakefile") );
0217 }
0218 
0219 void CustomMakeManager::createTargetItems(IProject* project, const Path& path, ProjectBaseItem* parent)
0220 {
0221     Q_ASSERT(isMakefile(path.lastPathSegment()));
0222     const auto targets = parseCustomMakeFile(path);
0223     for (const QString& target : targets) {
0224         if (!isValid(Path(parent->path(), target), false, project)) {
0225             continue;
0226         }
0227         new CustomMakeTargetItem( project, target, parent );
0228     }
0229 }
0230 
0231 ProjectFileItem* CustomMakeManager::createFileItem(IProject* project, const Path& path, ProjectBaseItem* parent)
0232 {
0233     auto* item = new ProjectFileItem(project, path, parent);
0234     if (isMakefile(path.lastPathSegment())){
0235         createTargetItems(project, path, parent);
0236     }
0237     return item;
0238 }
0239 
0240 void CustomMakeManager::reloadMakefile(ProjectFileItem* file)
0241 {
0242     if( !isMakefile(file->path().lastPathSegment())){
0243         return;
0244     }
0245     ProjectBaseItem* parent = file->parent();
0246     // remove the items that are Makefile targets
0247     const auto items = parent->children();
0248     for (ProjectBaseItem* item : items) {
0249         if (item->target()){
0250             delete item;
0251         }
0252     }
0253     // Recreate the targets.
0254     createTargetItems(parent->project(), file->path(), parent);
0255 }
0256 
0257 ProjectFolderItem* CustomMakeManager::createFolderItem(IProject* project, const Path& path, ProjectBaseItem* parent)
0258 {
0259     // TODO more faster algorithm. should determine whether this directory
0260     // contains makefile or not.
0261     return new KDevelop::ProjectBuildFolderItem( project, path, parent );
0262 }
0263 
0264 KDevelop::ProjectFolderItem* CustomMakeManager::import(KDevelop::IProject *project)
0265 {
0266     if( project->path().isRemote() )
0267     {
0268         //FIXME turn this into a real warning
0269         qCWarning(CUSTOMMAKE) << project->path() << "not a local file. Custom make support doesn't handle remote projects";
0270         return nullptr;
0271     }
0272 
0273     {
0274         QWriteLocker lock(&m_provider->m_lock);
0275         m_projectPaths.insert(project->path().path());
0276     }
0277 
0278     return AbstractFileManagerPlugin::import( project );
0279 }
0280 
0281 /////////////////////////////////////////////////////////////////////////////
0282 // private slots
0283 
0284 ///TODO: move to background thread, probably best would be to use a proper ParseJob
0285 QStringList CustomMakeManager::parseCustomMakeFile( const Path &makefile )
0286 {
0287     if( !makefile.isValid() )
0288         return QStringList();
0289 
0290     QStringList ret; // the list of targets
0291     QFile f( makefile.toLocalFile() );
0292     if ( !f.open( QIODevice::ReadOnly | QIODevice::Text ) )
0293     {
0294         qCDebug(CUSTOMMAKE) << "could not open" << makefile;
0295         return ret;
0296     }
0297 
0298     QRegExp targetRe(QStringLiteral("^ *([^\\t$.#]\\S+) *:?:(?!=).*$"));
0299     targetRe.setMinimal( true );
0300 
0301     QString str;
0302     QTextStream stream( &f );
0303     while ( !stream.atEnd() )
0304     {
0305         str = stream.readLine();
0306 
0307         if ( targetRe.indexIn( str ) != -1 )
0308         {
0309             QString tmpTarget = targetRe.cap( 1 ).simplified();
0310             if ( ! ret.contains( tmpTarget ) )
0311                 ret.append( tmpTarget );
0312         }
0313     }
0314     f.close();
0315     return ret;
0316 }
0317 
0318 void CustomMakeManager::projectClosing(IProject* project)
0319 {
0320     QWriteLocker lock(&m_provider->m_lock);
0321     m_projectPaths.remove(project->path().path());
0322 }
0323 
0324 void CustomMakeManager::unload()
0325 {
0326   IDefinesAndIncludesManager::manager()->unregisterBackgroundProvider(m_provider.data());
0327 }
0328 
0329 KDevelop::Path CustomMakeManager::compiler(KDevelop::ProjectTargetItem* item) const
0330 {
0331     Q_UNUSED(item);
0332     return {};
0333 }
0334 
0335 #include "custommakemanager.moc"
0336 #include "moc_custommakemanager.cpp"