diff --git a/app/main.cpp b/app/main.cpp index f6df290122..1d16c63f26 100644 --- a/app/main.cpp +++ b/app/main.cpp @@ -1,722 +1,761 @@ /*************************************************************************** * Copyright 2003-2009 Alexander Dymo * * Copyright 2007 Ralf Habacker * * Copyright 2006-2007 Matt Rogers * * Copyright 2006-2007 Hamish Rodda * * Copyright 2005-2007 Adam Treat * * Copyright 2003-2007 Jens Dagerbo * * Copyright 2001-2002 Bernd Gehrmann * * Copyright 2001-2002 Matthias Hoelzer-Kluepfel * * Copyright 2003 Roberto Raggi * * Copyright 2010 Niko Sams * * Copyright 2015 Kevin Funk * * * * This program 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 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 Library 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 +#include "urlinfo.h" + #include #include - #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_LOGGING_CATEGORY(APP) Q_LOGGING_CATEGORY(APP, "kdevelop.app") #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kdevideextension.h" #if KDEVELOP_SINGLE_APP #include "qtsingleapplication.h" #endif #include #ifdef Q_OS_MAC #include #endif using namespace KDevelop; -class KDevelopApplication: +namespace { + +#if KDEVELOP_SINGLE_APP +QString serializeOpenFilesMessage(const QVector &infos) +{ + QByteArray message; + QDataStream stream(&message, QIODevice::WriteOnly); + stream << QByteArrayLiteral("open"); + stream << infos; + return QString::fromLatin1(message.toHex()); +} +#endif + +void openFiles(const QVector& infos) +{ + foreach (const UrlInfo& file, infos) { + if (!ICore::self()->documentController()->openDocument(file.url, file.cursor)) { + qWarning() << i18n("Could not open %1", file.url.toDisplayString(QUrl::PreferLocalFile)); + } + } +} + +} + +class KDevelopApplication: #if KDEVELOP_SINGLE_APP public SharedTools::QtSingleApplication #else public QApplication #endif { public: explicit KDevelopApplication(int &argc, char **argv, bool GUIenabled = true) #if KDEVELOP_SINGLE_APP : SharedTools::QtSingleApplication(QStringLiteral("KDevelop"), argc, argv) #else : QApplication(argc, argv, GUIenabled) #endif { +#if KDEVELOP_SINGLE_APP + Q_UNUSED(GUIenabled); +#endif + connect(this, &QGuiApplication::saveStateRequest, this, &KDevelopApplication::saveState); } +#if KDEVELOP_SINGLE_APP +public Q_SLOTS: + void remoteArguments(const QString &message, QObject *socket) + { + Q_UNUSED(socket); + + QByteArray ba = QByteArray::fromHex(message.toLatin1()); + QDataStream stream(ba); + QByteArray command; + stream >> command; + + qCDebug(APP) << "Received remote command: " << command; + + if (command == "open") { + QVector infos; + stream >> infos; + openFiles(infos); + } else { + qCWarning(APP) << "Unknown remote command: " << command; + } + } + + void fileOpenRequested(const QString &file) + { + openFiles({UrlInfo(file)}); + } +#endif + private Q_SLOTS: void saveState( QSessionManager& sm ) { if (KDevelop::Core::self() && KDevelop::Core::self()->sessionController()) { QString x11SessionId = QString("%1_%2").arg(sm.sessionId()).arg(sm.sessionKey()); QString kdevelopSessionId = KDevelop::Core::self()->sessionController()->activeSession()->id().toString(); sm.setRestartCommand(QStringList() << QCoreApplication::applicationFilePath() << "-session" << x11SessionId << "-s" << kdevelopSessionId); } } }; /// Tries to find a session identified by @p data in @p sessions. /// The @p data may be either a session's name or a string-representation of its UUID. /// @return pointer to the session or NULL if nothing appropriate has been found static const KDevelop::SessionInfo* findSessionInList( const SessionInfos& sessions, const QString& data ) { // We won't search a session without input data, since that could lead to false-positives // with unnamed sessions if( data.isEmpty() ) return 0; for( auto it = sessions.constBegin(); it != sessions.constEnd(); ++it ) { if ( ( it->name == data ) || ( it->uuid.toString() == data ) ) { const KDevelop::SessionInfo& sessionRef = *it; return &sessionRef; } } return 0; } - -// Represents a file to be opened, consisting of its URL and the cursor to jump to. -struct UrlInfo -{ - // Parses a file path argument and determines its line number and column and full path - UrlInfo(QString path = {}) - : cursor(KTextEditor::Cursor::invalid()) - { - if (!path.startsWith(QLatin1Char('/')) && QFileInfo(path).isRelative()) { - path = QDir::currentPath() + QLatin1Char('/') + path; - } - url = QUrl::fromUserInput(path); - - if (url.isLocalFile() && !QFile::exists(path)) { - // Allow opening specific lines in documents, like mydoc.cpp:10 - // also supports columns, i.e. mydoc.cpp:10:42 - static const QRegularExpression pattern(QStringLiteral(":(\\d+)(?::(\\d+))?$")); - const auto match = pattern.match(path); - if (match.isValid()) { - path.chop(match.capturedLength()); - int line = match.captured(1).toInt() - 1; - // don't use an invalid column when the line is valid - int column = qMax(0, match.captured(2).toInt() - 1); - url = QUrl::fromLocalFile(path); - cursor = {line, column}; - } - } - } - - QUrl url; - KTextEditor::Cursor cursor; -}; - /// Performs a DBus call to open the given @p files in the running kdev instance identified by @p pid /// Returns the exit status static int openFilesInRunningInstance(const QVector& files, qint64 pid) { const QString service = QString("org.kdevelop.kdevelop-%1").arg(pid); QDBusInterface iface(service, "/org/kdevelop/DocumentController", "org.kdevelop.DocumentController"); QStringList urls; bool errors_occured = false; foreach ( const UrlInfo& file, files ) { QDBusReply result = iface.call("openDocumentSimple", file.url.toString(), file.cursor.line(), file.cursor.column()); if ( ! result.value() ) { QTextStream err(stderr); err << i18n("Could not open file %1.", file.url.toDisplayString(QUrl::PreferLocalFile)) << "\n"; errors_occured = true; } } // make the window visible QDBusMessage makeVisible = QDBusMessage::createMethodCall( service, "/kdevelop/MainWindow", "org.kdevelop.MainWindow", "ensureVisible" ); QDBusConnection::sessionBus().asyncCall( makeVisible ); return errors_occured ? 1 : 0; } /// Gets the PID of a running KDevelop instance, eventually asking the user if there is more than one. /// Returns -1 in case there are no running sessions. static qint64 getRunningSessionPid() { SessionInfos candidates; foreach( const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos() ) { if( KDevelop::SessionController::isSessionRunning(si.uuid.toString()) ) { candidates << si; } } if ( candidates.isEmpty() ) { return -1; } QString sessionUuid; if ( candidates.size() == 1 ) { sessionUuid = candidates.first().uuid.toString(); } else { const QString title = i18n("Select the session to open the document in"); sessionUuid = KDevelop::SessionController::showSessionChooserDialog(title, true); } return KDevelop::SessionController::sessionRunInfo(sessionUuid).holderPid; } static QString findSessionId(const QString& session) { //If there is a session and a project with the same name, always open the session //regardless of the order encountered QString projectAsSession; foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos()) { if ( session == si.name || session == si.uuid.toString() ) { return si.uuid.toString(); } else if (projectAsSession.isEmpty()) { foreach(const QUrl& k, si.projects) { QString fn(k.fileName()); fn = fn.left(fn.indexOf('.')); if ( session == fn ) { projectAsSession = si.uuid.toString(); } } } } if (projectAsSession.isEmpty()) { QTextStream qerr(stderr); qerr << endl << i18n("Cannot open unknown session %1. See `--list-sessions` switch for available sessions or use `-n` to create a new one.", session) << endl; } return projectAsSession; } static void tryLoadIconResources() { const QString breezeIcons = QStandardPaths::locate(QStandardPaths::DataLocation, QStringLiteral("icons/breeze/breeze-icons.rcc")); if (!breezeIcons.isEmpty() && QFile::exists(breezeIcons)) { qCDebug(APP) << "Loading icons rcc:" << breezeIcons; // prepend /icons to the root to comply with KIcon* machinery QResource::registerResource(breezeIcons, QStringLiteral("/icons/breeze")); QIcon::setThemeSearchPaths(QStringList() << QStringLiteral(":/icons")); } } static qint64 findSessionPid(const QString &sessionId) { KDevelop::SessionRunInfo sessionInfo = KDevelop::SessionController::sessionRunInfo( sessionId ); return sessionInfo.holderPid; } int main( int argc, char *argv[] ) { QElapsedTimer timer; timer.start(); // TODO: Maybe generalize, add KDEVELOP_STANDALONE build option #if defined(Q_OS_WIN) || defined(Q_OS_MAC) qputenv("KDE_FORK_SLAVES", "1"); // KIO slaves will be forked off instead of being started via DBus #endif // Useful for valgrind runs, just `export KDEV_DISABLE_JIT=1` if (qEnvironmentVariableIsSet("KDEV_DISABLE_JIT")) { qputenv("KDEV_DISABLE_WELCOMEPAGE", "1"); qputenv("QT_ENABLE_REGEXP_JIT", "0"); } // Don't show any debug output by default. // If you need to enable additional logging for debugging use a rules file // as explained in the QLoggingCategory documentation: // http://qt-project.org/doc/qt-5/qloggingcategory.html#logging-rules QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\n")); KLocalizedString::setApplicationDomain("kdevelop"); QCoreApplication::setAttribute(Qt::AA_DontCreateNativeWidgetSiblings); #ifdef Q_OS_MAC CFBundleRef mainBundle = CFBundleGetMainBundle(); if (mainBundle) { // get the application's Info Dictionary. For app bundles this would live in the bundle's Info.plist, // for regular executables it is obtained in another way. CFMutableDictionaryRef infoDict = (CFMutableDictionaryRef) CFBundleGetInfoDictionary(mainBundle); if (infoDict) { // Try to prevent App Nap on OS X. This can be tricky in practice, at least in 10.9 . CFDictionarySetValue(infoDict, CFSTR("NSAppSleepDisabled"), kCFBooleanTrue); CFDictionarySetValue(infoDict, CFSTR("NSSupportsAutomaticTermination"), kCFBooleanFalse); } } #endif static const char description[] = I18N_NOOP( "The KDevelop Integrated Development Environment" ); KAboutData aboutData( "kdevelop", i18n( "KDevelop" ), QByteArray(VERSION), i18n(description), KAboutLicense::GPL, i18n("Copyright 1999-2015, The KDevelop developers"), QString(), "http://www.kdevelop.org/"); aboutData.setDesktopFileName(QStringLiteral("org.kde.kdevelop.desktop")); aboutData.addAuthor( i18n("Aleix Pol Gonzalez"), i18n( "Co-Maintainer, CMake Support, Run Support, Kross Support" ), "aleixpol@gmail.com" ); aboutData.addAuthor( i18n("Milian Wolff"), i18n( "Co-Maintainer, C++/Clang, Generic manager, Webdevelopment Plugins, Snippets, Performance" ), "mail@milianw.de" ); aboutData.addAuthor( i18n("Kevin Funk"), i18n( "C++/Clang, General Improvements, QA, Windows Support" ), "kfunk@kde.org" ); aboutData.addAuthor( i18n("Olivier JG"), i18n( "C++/Clang, DUChain, Bug Fixes" ), "olivier.jg@gmail.com" ); aboutData.addAuthor( i18n("Sven Brauch"), i18n( "Python Support, User Interface improvements" ), "svenbrauch@gmail.com" ); aboutData.addAuthor( i18n("Andreas Pakulat"), i18n( "Architecture, VCS Support, Project Management Support, QMake Projectmanager" ), "apaku@gmx.de" ); aboutData.addAuthor( i18n("Alexander Dymo"), i18n( "Architecture, Sublime UI, Ruby support" ), "adymo@kdevelop.org" ); aboutData.addAuthor( i18n("David Nolden"), i18n( "Definition-Use Chain, C++ Support, Code Navigation, Code Completion, Coding Assistance, Refactoring" ), "david.nolden.kdevelop@art-master.de" ); aboutData.addAuthor( i18n("Vladimir Prus"), i18n( "GDB integration" ), "ghost@cs.msu.su" ); aboutData.addAuthor( i18n("Hamish Rodda"), i18n( "Text editor integration, definition-use chain" ), "rodda@kde.org" ); aboutData.addAuthor( i18n("Amilcar do Carmo Lucas"), i18n( "Website admin, API documentation, Doxygen and autoproject patches" ), "amilcar@kdevelop.org" ); aboutData.addAuthor( i18n("Niko Sams"), i18n( "GDB integration, Webdevelopment Plugins" ), "niko.sams@gmail.com" ); aboutData.addCredit( i18n("Matt Rogers"), QString(), "mattr@kde.org"); aboutData.addCredit( i18n("Cédric Pasteur"), i18n("astyle and indent support"), "cedric.pasteur@free.fr" ); aboutData.addCredit( i18n("Evgeniy Ivanov"), i18n("Distributed VCS, Git, Mercurial"), "powerfox@kde.ru" ); // QTest integration is separate in playground currently. //aboutData.addCredit( i18n("Manuel Breugelmanns"), i18n( "Veritas, QTest integration"), "mbr.nxi@gmail.com" ); aboutData.addCredit( i18n("Robert Gruber") , i18n( "SnippetPart, debugger and usability patches" ), "rgruber@users.sourceforge.net" ); aboutData.addCredit( i18n("Dukju Ahn"), i18n( "Subversion plugin, Custom Make Manager, Overall improvements" ), "dukjuahn@gmail.com" ); aboutData.addCredit( i18n("Harald Fernengel"), i18n( "Ported to Qt 3, patches, valgrind, diff and perforce support" ), "harry@kdevelop.org" ); aboutData.addCredit( i18n("Roberto Raggi"), i18n( "C++ parser" ), "roberto@kdevelop.org" ); aboutData.addCredit( i18n("The KWrite authors"), i18n( "Kate editor component" ), "kwrite-devel@kde.org" ); aboutData.addCredit( i18n("Nokia Corporation/Qt Software"), i18n( "Designer code" ), "qt-info@nokia.com" ); aboutData.addCredit( i18n("Contributors to older versions:"), QString(), "" ); aboutData.addCredit( i18n("Bernd Gehrmann"), i18n( "Initial idea, basic architecture, much initial source code" ), "bernd@kdevelop.org" ); aboutData.addCredit( i18n("Caleb Tennis"), i18n( "KTabBar, bugfixes" ), "caleb@aei-tech.com" ); aboutData.addCredit( i18n("Richard Dale"), i18n( "Java & Objective C support" ), "Richard_Dale@tipitina.demon.co.uk" ); aboutData.addCredit( i18n("John Birch"), i18n( "Debugger frontend" ), "jbb@kdevelop.org" ); aboutData.addCredit( i18n("Sandy Meier"), i18n( "PHP support, context menu stuff" ), "smeier@kdevelop.org" ); aboutData.addCredit( i18n("Kurt Granroth"), i18n( "KDE application templates" ), "kurth@granroth.org" ); aboutData.addCredit( i18n("Ian Reinhart Geiser"), i18n( "Dist part, bash support, application templates" ), "geiseri@yahoo.com" ); aboutData.addCredit( i18n("Matthias Hoelzer-Kluepfel"), i18n( "Several components, htdig indexing" ), "hoelzer@kde.org" ); aboutData.addCredit( i18n("Victor Roeder"), i18n( "Help with Automake manager and persistent class store" ), "victor_roeder@gmx.de" ); aboutData.addCredit( i18n("Simon Hausmann"), i18n( "Help with KParts infrastructure" ), "hausmann@kde.org" ); aboutData.addCredit( i18n("Oliver Kellogg"), i18n( "Ada support" ), "okellogg@users.sourceforge.net" ); aboutData.addCredit( i18n("Jakob Simon-Gaarde"), i18n( "QMake projectmanager" ), "jsgaarde@tdcspace.dk" ); aboutData.addCredit( i18n("Falk Brettschneider"), i18n( "MDI modes, QEditor, bugfixes" ), "falkbr@kdevelop.org" ); aboutData.addCredit( i18n("Mario Scalas"), i18n( "PartExplorer, redesign of CvsPart, patches, bugs(fixes)" ), "mario.scalas@libero.it" ); aboutData.addCredit( i18n("Jens Dagerbo"), i18n( "Replace, Bookmarks, FileList and CTags2 plugins. Overall improvements and patches" ), "jens.dagerbo@swipnet.se" ); aboutData.addCredit( i18n("Julian Rockey"), i18n( "Filecreate part and other bits and patches" ), "linux@jrockey.com" ); aboutData.addCredit( i18n("Ajay Guleria"), i18n( "ClearCase support" ), "ajay_guleria@yahoo.com" ); aboutData.addCredit( i18n("Marek Janukowicz"), i18n( "Ruby support" ), "child@t17.ds.pwr.wroc.pl" ); aboutData.addCredit( i18n("Robert Moniot"), i18n( "Fortran documentation" ), "moniot@fordham.edu" ); aboutData.addCredit( i18n("Ka-Ping Yee"), i18n( "Python documentation utility" ), "ping@lfw.org" ); aboutData.addCredit( i18n("Dimitri van Heesch"), i18n( "Doxygen wizard" ), "dimitri@stack.nl" ); aboutData.addCredit( i18n("Hugo Varotto"), i18n( "Fileselector component" ), "hugo@varotto-usa.com" ); aboutData.addCredit( i18n("Matt Newell"), i18n( "Fileselector component" ), "newellm@proaxis.com" ); aboutData.addCredit( i18n("Daniel Engelschalt"), i18n( "C++ code completion, persistent class store" ), "daniel.engelschalt@gmx.net" ); aboutData.addCredit( i18n("Stephane Ancelot"), i18n( "Patches" ), "sancelot@free.fr" ); aboutData.addCredit( i18n("Jens Zurheide"), i18n( "Patches" ), "jens.zurheide@gmx.de" ); aboutData.addCredit( i18n("Luc Willems"), i18n( "Help with Perl support" ), "Willems.luc@pandora.be" ); aboutData.addCredit( i18n("Marcel Turino"), i18n( "Documentation index view" ), "M.Turino@gmx.de" ); aboutData.addCredit( i18n("Yann Hodique"), i18n( "Patches" ), "Yann.Hodique@lifl.fr" ); aboutData.addCredit( i18n("Tobias Gl\303\244\303\237er") , i18n( "Documentation Finder, qmake projectmanager patches, usability improvements, bugfixes ... " ), "tobi.web@gmx.de" ); aboutData.addCredit( i18n("Andreas Koepfle") , i18n( "QMake project manager patches" ), "koepfle@ti.uni-mannheim.de" ); aboutData.addCredit( i18n("Sascha Cunz") , i18n( "Cleanup and bugfixes for qEditor, AutoMake and much other stuff" ), "mail@sacu.de" ); aboutData.addCredit( i18n("Zoran Karavla"), i18n( "Artwork for the ruby language" ), "webmaster@the-error.net", "http://the-error.net" ); //we can't use KCmdLineArgs as it doesn't allow arguments for the debugee //so lookup the --debug switch and eat everything behind by decrementing argc //debugArgs is filled with args after --debug QStringList debugArgs; QString debugeeName; { bool debugFound = false; int c = argc; for (int i=0; i < c; ++i) { if (debugFound) { debugArgs << argv[i]; } else if (qstrcmp(argv[i], "--debug") == 0 || qstrcmp(argv[i], "-d") == 0) { if (argc <= i+1) { argc = i + 1; } else { i++; argc = i + 1; } debugFound = true; } else if (QString(argv[i]).startsWith("--debug=")) { argc = i + 1; debugFound = true; } } } KDevelopApplication app(argc, argv); KCrash::initialize(); tryLoadIconResources(); Kdelibs4ConfigMigrator migrator(QStringLiteral("kdevelop")); migrator.setConfigFiles({QStringLiteral("kdeveloprc")}); migrator.setUiFiles({QStringLiteral("kdevelopui.rc")}); migrator.migrate(); // High DPI support app.setAttribute(Qt::AA_UseHighDpiPixmaps, true); qCDebug(APP) << "Startup"; QCommandLineParser parser; KAboutData::setApplicationData(aboutData); parser.addVersionOption(); parser.addHelpOption(); aboutData.setupCommandLine(&parser); parser.addOption(QCommandLineOption{QStringList{"n", "new-session"}, i18n("Open KDevelop with a new session using the given name."), "name"}); parser.addOption(QCommandLineOption{QStringList{"s", "open-session"}, i18n("Open KDevelop with the given session.\n" "You can pass either hash or the name of the session." ), "session"}); parser.addOption(QCommandLineOption{QStringList{"rm", "remove-session"}, i18n("Delete the given session.\n" "You can pass either hash or the name of the session." ), "session"}); parser.addOption(QCommandLineOption{QStringList{"ps", "pick-session"}, i18n("Shows all available sessions and lets you select one to open.")}); parser.addOption(QCommandLineOption{QStringList{"pss", "pick-session-shell"}, i18n("List all available sessions on shell and lets you select one to open.")}); parser.addOption(QCommandLineOption{QStringList{"l", "list-sessions"}, i18n("List available sessions and quit.")}); parser.addOption(QCommandLineOption{QStringList{"p", "project"}, i18n("Open KDevelop and load the given project."), "project"}); parser.addOption(QCommandLineOption{QStringList{"d", "debug"}, i18n("Start debugging an application in KDevelop with the given debugger.\n" "The binary that should be debugged must follow - including arguments.\n" "Example: kdevelop --debug gdb myapp --foo bar"), "debugger"}); // this is used by the 'kdevelop!' script to retrieve the pid of a KDEVELOP // instance. When this is called, then we should just print the PID on the // standard-output. If a session is specified through open-session, then // we should return the PID of that session. Otherwise, if only a single // session is running, then we should just return the PID of that session. // Otherwise, we should print a command-line session-chooser dialog ("--pss"), // which only shows the running sessions, and the user can pick one. parser.addOption(QCommandLineOption{QStringList{"pid"}}); parser.addPositionalArgument("files", i18n( "Files to load" ), "[FILE...]"); // The session-controller needs to arguments to eventually pass them to newly opened sessions KDevelop::SessionController::setArguments(argc, argv); parser.process(app); aboutData.processCommandLine(&parser); if(parser.isSet("list-sessions")) { QTextStream qout(stdout); qout << endl << ki18n("Available sessions (use '-s HASH' or '-s NAME' to open a specific one):").toString() << endl << endl; qout << QString("%1").arg(ki18n("Hash").toString(), -38) << '\t' << ki18n("Name: Opened Projects").toString() << endl; foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos()) { if ( si.name.isEmpty() && si.projects.isEmpty() ) { continue; } qout << si.uuid.toString() << '\t' << si.description; if(KDevelop::SessionController::isSessionRunning(si.uuid.toString())) qout << " " << i18n("[running]"); qout << endl; } return 0; } // Handle extra arguments, which stand for files to open QVector initialFiles; foreach (const QString &file, parser.positionalArguments()) { initialFiles.append(UrlInfo(file)); } - if ( ! initialFiles.isEmpty() && ! parser.isSet("new-session") ) - { + + if (!initialFiles.isEmpty() && !parser.isSet("new-session")) { +#if KDEVELOP_SINGLE_APP + if (app.isRunning()) { + bool success = app.sendMessage(serializeOpenFilesMessage(initialFiles)); + if (success) { + return 0; + } + } +#else qint64 pid = -1; if (parser.isSet("open-session")) { const QString session = findSessionId(parser.value("open-session")); if (session.isEmpty()) { return 1; } else if (KDevelop::SessionController::isSessionRunning(session)) { pid = findSessionPid(session); } } else { pid = getRunningSessionPid(); } if ( pid > 0 ) { return openFilesInRunningInstance(initialFiles, pid); } // else there are no running sessions, and the generated list of files will be opened below. +#endif } // if empty, restart kdevelop with last active session, see SessionController::defaultSessionId QString session; uint nRunningSessions = 0; foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos()) if(KDevelop::SessionController::isSessionRunning(si.uuid.toString())) ++nRunningSessions; // also show the picker dialog when a pid shall be retrieved and multiple // sessions are running. if(parser.isSet("pss") || (parser.isSet("pid") && !parser.isSet("open-session") && !parser.isSet("ps") && nRunningSessions > 1)) { QTextStream qerr(stderr); SessionInfos candidates; foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos()) if( (!si.name.isEmpty() || !si.projects.isEmpty() || parser.isSet("pid")) && (!parser.isSet("pid") || KDevelop::SessionController::isSessionRunning(si.uuid.toString()))) candidates << si; if(candidates.size() == 0) { qerr << "no session available" << endl; return 1; } if(candidates.size() == 1 && parser.isSet("pid")) { session = candidates[0].uuid.toString(); }else{ for(int i = 0; i < candidates.size(); ++i) qerr << "[" << i << "]: " << candidates[i].description << endl; int chosen; std::cin >> chosen; if(std::cin.good() && (chosen >= 0 && chosen < candidates.size())) { session = candidates[chosen].uuid.toString(); }else{ qerr << "invalid selection" << endl; return 1; } } } if(parser.isSet("ps")) { bool onlyRunning = parser.isSet("pid"); session = KDevelop::SessionController::showSessionChooserDialog(i18n("Select the session you would like to use"), onlyRunning); if(session.isEmpty()) return 1; } if ( parser.isSet("debug") ) { if ( debugArgs.isEmpty() ) { QTextStream qerr(stderr); qerr << endl << i18nc("@info:shell", "Specify the binary you want to debug.") << endl; return 1; } debugeeName = i18n("Debug %1", QUrl( debugArgs.first() ).fileName()); session = debugeeName; } else if ( parser.isSet("new-session") ) { session = parser.value("new-session"); foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos()) { if ( session == si.name ) { QTextStream qerr(stderr); qerr << endl << i18n("A session with the name %1 exists already. Use the -s switch to open it.", session) << endl; return 1; } } // session doesn't exist, we can create it } else if ( parser.isSet("open-session") ) { session = findSessionId(parser.value("open-session")); if (session.isEmpty()) { return 1; } } else if ( parser.isSet("remove-session") ) { session = parser.value("remove-session"); auto si = findSessionInList(KDevelop::SessionController::availableSessionInfos(), session); if (!si) { QTextStream qerr(stderr); qerr << endl << i18n("No session with the name %1 exists.", session) << endl; return 1; } auto sessionLock = KDevelop::SessionController::tryLockSession(si->uuid.toString()); if (!sessionLock.lock) { QTextStream qerr(stderr); qerr << endl << i18n("Could not lock session %1 for deletion.", session) << endl; return 1; } KDevelop::SessionController::deleteSessionFromDisk(sessionLock.lock); QTextStream qout(stdout); qout << endl << i18n("Session with name %1 was successfully removed.", session) << endl; return 0; } if(parser.isSet("pid")) { if (session.isEmpty()) { // just pick the first running session foreach(const KDevelop::SessionInfo& si, KDevelop::SessionController::availableSessionInfos()) if(KDevelop::SessionController::isSessionRunning(si.uuid.toString())) session = si.uuid.toString(); } SessionInfos sessions = KDevelop::SessionController::availableSessionInfos(); const KDevelop::SessionInfo* sessionData = findSessionInList( sessions, session ); if( !sessionData ) { qCritical() << "session not given or does not exist"; return 5; } const auto pid = findSessionPid(sessionData->uuid.toString()); if (pid > 0) { // Print the PID and we're ready std::cout << pid << std::endl; return 0; } else { qCritical() << sessionData->uuid.toString() << sessionData->name << "is not running"; return 5; } } KDevIDEExtension::init(); if(!Core::initialize(nullptr, Core::Default, session)) return 5; // register a DBUS service for this process, so that we can open files in it from other invocations QDBusConnection::sessionBus().registerService(QString("org.kdevelop.kdevelop-%1").arg(app.applicationPid())); // TODO: port to kf5 // KGlobal::locale()->insertCatalog( Core::self()->componentData().catalogName() ); Core* core = Core::self(); if (!QProcessEnvironment::systemEnvironment().contains("KDEV_DISABLE_WELCOMEPAGE")) { core->pluginController()->loadPlugin("KDevWelcomePage"); } QStringList projectNames = parser.values("project"); if(!projectNames.isEmpty()) { foreach(const QString& p, projectNames) { QFileInfo info( p ); if( info.suffix() == "kdev4" ) { // make sure the project is not already opened by the session controller bool shouldOpen = true; Path path(info.absoluteFilePath()); foreach(KDevelop::IProject* p, core->projectController()->projects()) { if (p->projectFile() == path) { shouldOpen = false; break; } } if (shouldOpen) { core->projectController()->openProject( path.toUrl() ); } } } } if ( parser.isSet("debug") ) { Q_ASSERT( !debugeeName.isEmpty() ); QString launchName = debugeeName; KDevelop::LaunchConfiguration* launch = 0; qCDebug(APP) << launchName; foreach (KDevelop::LaunchConfiguration *l, core->runControllerInternal()->launchConfigurationsInternal()) { qCDebug(APP) << l->name(); if (l->name() == launchName) { launch = l; } } KDevelop::LaunchConfigurationType *type = 0; foreach (KDevelop::LaunchConfigurationType *t, core->runController()->launchConfigurationTypes()) { qCDebug(APP) << t->id(); if (t->id() == "Native Application") { type = t; break; } } if (!type) { QTextStream qerr(stderr); qerr << endl << i18n("Cannot find native launch configuration type") << endl; return 1; } if (launch && launch->type()->id() != "Native Application") launch = 0; if (launch && launch->launcherForMode("debug") != parser.value("debug")) launch = 0; if (!launch) { qCDebug(APP) << launchName << "not found, creating a new one"; QPair launcher; launcher.first = "debug"; foreach (KDevelop::ILauncher *l, type->launchers()) { if (l->id() == parser.value("debug")) { if (l->supportedModes().contains("debug")) { launcher.second = l->id(); } } } if (launcher.second.isEmpty()) { QTextStream qerr(stderr); qerr << endl << i18n("Cannot find launcher %1", parser.value("debug")) << endl; return 1; } KDevelop::ILaunchConfiguration* ilaunch = core->runController()->createLaunchConfiguration(type, launcher, 0, launchName); launch = dynamic_cast(ilaunch); } type->configureLaunchFromCmdLineArguments(launch->config(), debugArgs); launch->config().writeEntry("Break on Start", true); core->runControllerInternal()->setDefaultLaunch(launch); core->runControllerInternal()->execute("debug", launch); } else { - foreach ( const UrlInfo& file, initialFiles ) { - if(!core->documentController()->openDocument(file.url, file.cursor)) { - qWarning() << i18n("Could not open %1", file.url.toDisplayString(QUrl::PreferLocalFile)); - } - } + openFiles(initialFiles); } +#if KDEVELOP_SINGLE_APP + // Set up remote arguments. + QObject::connect(&app, &SharedTools::QtSingleApplication::messageReceived, + &app, &KDevelopApplication::remoteArguments); + + QObject::connect(&app, &SharedTools::QtSingleApplication::fileOpenRequest, + &app, &KDevelopApplication::fileOpenRequested); +#endif + + #ifdef WITH_WELCOMEPAGE // make it possible to disable the welcome page, useful for valgrind runs e.g. if (!qEnvironmentVariableIsSet("KDEV_DISABLE_WELCOMEPAGE")) { trySetupWelcomePageView(); } #endif qCDebug(APP) << "Done startup" << "- took:" << timer.elapsed() << "ms"; timer.invalidate(); return app.exec(); } diff --git a/app/urlinfo.h b/app/urlinfo.h new file mode 100644 index 0000000000..dbcaf0c26b --- /dev/null +++ b/app/urlinfo.h @@ -0,0 +1,126 @@ +/* This file is part of the KDE project + Copyright (C) 2015 Milian Wolff + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 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 6 of version 3 of the license. + + 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 + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library. If not, see . +*/ + +#ifndef URLINFO_H +#define URLINFO_H + +#include + +#include +#include +#include +#include +#include + +/** + * Represents a file to be opened, consisting of its URL and the cursor to jump to. + */ +class UrlInfo +{ +public: + /** + * Parses a file path argument and determines its line number and column and full path + * @param path path passed on e.g. command line to parse into an URL + */ + UrlInfo(QString path = QString()) + : cursor(KTextEditor::Cursor::invalid()) + { + /** + * first try: just check if the path is an existing file + */ + if (QFile::exists(path)) { + /** + * create absolute file path, we will e.g. pass this over dbus to other processes + * and then we are done, no cursor can be detected here! + */ + url = QUrl::fromLocalFile(QDir::current().absoluteFilePath(path)); + return; + } + + /** + * ok, the path as is, is no existing file, now, cut away :xx:yy stuff as cursor + * this will make test:50 to test with line 50 + */ + const auto match = QRegularExpression(QStringLiteral(":(\\d+)(?::(\\d+))?:?$")).match(path); + if (match.isValid()) { + /** + * cut away the line/column specification from the path + */ + path.chop(match.capturedLength()); + + /** + * set right cursor position + * don't use an invalid column when the line is valid + */ + const int line = match.captured(1).toInt() - 1; + const int column = qMax(0, match.captured(2).toInt() - 1); + cursor.setPosition(line, column); + } + + /** + * construct url: + * - make relative paths absolute using the current working directory + * - prefer local file, if in doubt! + */ + url = QUrl::fromUserInput(path, QDir::currentPath(), QUrl::AssumeLocalFile); + + /** + * in some cases, this will fail, e.g. if you have line/column specs like test.c:10:1 + * => fallback: assume a local file and just convert it to an url + */ + if (!url.isValid()) { + /** + * create absolute file path, we will e.g. pass this over dbus to other processes + */ + url = QUrl::fromLocalFile(QDir::current().absoluteFilePath(path)); + } + } + + /** + * url computed out of the passed path + */ + QUrl url; + + /** + * initial cursor position, if any found inside the path as line/colum specification at the end + */ + KTextEditor::Cursor cursor; +}; + +QDataStream& operator<<(QDataStream& stream, const UrlInfo& info) +{ + stream << info.url; + stream << info.cursor.line(); + stream << info.cursor.column(); + return stream; +} + +QDataStream& operator>>(QDataStream& stream, UrlInfo& info) +{ + stream >> info.url; + int line, column; + stream >> line; + stream >> column; + info.cursor.setLine(line); + info.cursor.setColumn(column); + return stream; +} + +#endif // URLINFO_H diff --git a/debuggers/common/stty.cpp b/debuggers/common/stty.cpp index 70e7aa8320..dd3d61428e 100644 --- a/debuggers/common/stty.cpp +++ b/debuggers/common/stty.cpp @@ -1,348 +1,347 @@ /*************************************************************************** begin : Mon Sep 13 1999 copyright : (C) 1999 by John Birch email : jbb@kdevelop.org This code was originally written by Judin Maxim, from the KDEStudio project. It was then updated with later code from konsole (KDE). It has also been enhanced with an idea from the code in kdbg written by Johannes Sixt ***************************************************************************/ /*************************************************************************** * * * 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. * * * ***************************************************************************/ #ifdef HAVE_CONFIG_H #include #endif #ifdef __osf__ #define _XOPEN_SOURCE_EXTENDED #define O_NDELAY O_NONBLOCK #endif #include #include #include #include #include #include #ifdef HAVE_SYS_STROPTS_H #include #define _NEW_TTY_CTRL #endif #include #include #include #include #include #include #include #include #include #include #if defined (_HPUX_SOURCE) #define _TERMIOS_INCLUDED #include #endif #include #include #include #include #include #include #include #include #include "stty.h" #include "debuglog.h" #define PTY_FILENO 3 #define BASE_CHOWN "konsole_grantpty" #include using namespace KDevMI; static int chownpty(int fd, int grant) // param fd: the fd of a master pty. // param grant: 1 to grant, 0 to revoke // returns 1 on success 0 on fail { void(*tmp)(int) = signal(SIGCHLD,SIG_DFL); pid_t pid = fork(); if (pid < 0) { signal(SIGCHLD,tmp); return 0; } if (pid == 0) { /* We pass the master pseudo terminal as file descriptor PTY_FILENO. */ if (fd != PTY_FILENO && dup2(fd, PTY_FILENO) < 0) ::exit(1); QString path = QStandardPaths::findExecutable(BASE_CHOWN); execle(QFile::encodeName(path), BASE_CHOWN, grant?"--grant":"--revoke", (void *)0, NULL); ::exit(1); // should not be reached } if (pid > 0) { int w; // retry: int rc = waitpid (pid, &w, 0); if (rc != pid) ::exit(1); // { // signal from other child, behave like catchChild. // // guess this gives quite some control chaos... // Shell* sh = shells.indexOf(rc); // if (sh) { shells.remove(rc); sh->doneShell(w); } // goto retry; // } signal(SIGCHLD,tmp); return (rc != -1 && WIFEXITED(w) && WEXITSTATUS(w) == 0); } signal(SIGCHLD,tmp); return 0; //dummy. } // ************************************************************************** STTY::STTY(bool ext, const QString &termAppName) : QObject(), out(0), ttySlave(""), m_externalTerminal(0), external_(ext) { if (ext) { findExternalTTY(termAppName); } else { fout = findTTY(); if (fout >= 0) { ttySlave = QString(tty_slave); out = new QSocketNotifier(fout, QSocketNotifier::Read, this); connect( out, &QSocketNotifier::activated, this, &STTY::OutReceived ); } } } // ************************************************************************** STTY::~STTY() { if (out) { ::close(fout); delete out; } } // ************************************************************************** int STTY::findTTY() { int ptyfd = -1; bool needGrantPty = true; // Find a master pty that we can open //////////////////////////////// #ifdef __sgi__ ptyfd = open("/dev/ptmx",O_RDWR); #elif defined(Q_OS_MAC) ptyfd = posix_openpt(O_RDWR); #endif #if defined(__sgi__) || defined(Q_OS_MAC) if (ptyfd == -1) { perror("Can't open a pseudo teletype"); return(-1); } else if (ptyfd >= 0) { strncpy(tty_slave, ptsname(ptyfd), 50); grantpt(ptyfd); unlockpt(ptyfd); needGrantPty = false; } #endif // first we try UNIX PTY's #ifdef TIOCGPTN strcpy(pty_master,"/dev/ptmx"); strcpy(tty_slave,"/dev/pts/"); ptyfd = open(pty_master,O_RDWR); if (ptyfd >= 0) { // got the master pty int ptyno; if (ioctl(ptyfd, TIOCGPTN, &ptyno) == 0) { struct stat sbuf; sprintf(tty_slave,"/dev/pts/%d",ptyno); if (stat(tty_slave,&sbuf) == 0 && S_ISCHR(sbuf.st_mode)) needGrantPty = false; else { close(ptyfd); ptyfd = -1; } } else { close(ptyfd); ptyfd = -1; } } #endif #if defined(_SCO_DS) || defined(__USLC__) /* SCO OSr5 and UnixWare */ if (ptyfd < 0) { for (int idx = 0; idx < 256; idx++) { sprintf(pty_master, "/dev/ptyp%d", idx); sprintf(tty_slave, "/dev/ttyp%d", idx); if (access(tty_slave, F_OK) < 0) { idx = 256; break; } if ((ptyfd = open (pty_master, O_RDWR)) >= 0) { if (access (tty_slave, R_OK|W_OK) == 0) break; close(ptyfd); ptyfd = -1; } } } #endif if (ptyfd < 0) { /// \FIXME Linux, Trouble on other systems? for (const char* s3 = "pqrstuvwxyzabcde"; *s3 != 0; s3++) { for (const char* s4 = "0123456789abcdef"; *s4 != 0; s4++) { sprintf(pty_master,"/dev/pty%c%c",*s3,*s4); sprintf(tty_slave,"/dev/tty%c%c",*s3,*s4); if ((ptyfd = open(pty_master, O_RDWR)) >= 0) { if (geteuid() == 0 || access(tty_slave, R_OK|W_OK) == 0) break; close(ptyfd); ptyfd = -1; } } if (ptyfd >= 0) break; } } if (ptyfd >= 0) { if (needGrantPty && !chownpty(ptyfd, true)) { fprintf(stderr,"kdevelop: chownpty failed for device %s::%s.\n",pty_master,tty_slave); fprintf(stderr," : This means the session can be eavesdroped.\n"); fprintf(stderr," : Make sure konsole_grantpty is installed and setuid root.\n"); } ::fcntl(ptyfd, F_SETFL, O_NDELAY); #ifdef TIOCSPTLCK int flag = 0; ioctl(ptyfd, TIOCSPTLCK, &flag); // unlock pty #endif } if (ptyfd==-1) { m_lastError = i18n("Cannot use the tty* or pty* devices.\n" "Check the settings on /dev/tty* and /dev/pty*\n" "As root you may need to \"chmod ug+rw\" tty* and pty* devices " "and/or add the user to the tty group using " "\"usermod -aG tty username\"."); } return ptyfd; } // ************************************************************************** void STTY::OutReceived(int f) { char buf[1024]; int n; // read until socket is empty. We shouldn't be receiving a continuous // stream of data, so the loop is unlikely to cause problems. while ((n = ::read(f, buf, sizeof(buf)-1)) > 0) { *(buf+n) = 0; // a standard string QByteArray ba(buf); emit OutOutput(ba); } // Note: for some reason, n can be 0 here. // I can understand that non-blocking read returns 0, // but I don't understand how OutRecieved can be even // called when there's no input. if (n == 0 /* eof */ || (n == -1 && errno != EAGAIN)) { // Found eof or error. Disable socket notifier, otherwise Qt // will repeatedly call this method, eating CPU // cycles. out->setEnabled(false); } } void STTY::readRemaining() { if (!external_) OutReceived(fout); } bool STTY::findExternalTTY(const QString& termApp) { QString appName(termApp.isEmpty() ? QString("xterm") : termApp); if (QStandardPaths::findExecutable(appName).isEmpty()) { m_lastError = i18n("%1 is incorrect terminal name", termApp); return false; } QTemporaryFile file; if (!file.open()) { m_lastError = i18n("Can't create a temporary file"); return false; } m_externalTerminal.reset(new QProcess(this)); - KDevelop::restoreSystemEnvironment(m_externalTerminal.data()); if (appName == "konsole") { m_externalTerminal->start(appName, QStringList() << "-e" << "sh" << "-c" << "tty>" + file.fileName() + ";exec<&-;exec>&-;while :;do sleep 3600;done"); } else if (appName == "xfce4-terminal") { m_externalTerminal->start(appName, QStringList() << "-e" << " sh -c \"tty>" + file.fileName() + ";\"\"<&\\-\"\">&\\-;\"\"while :;\"\"do sleep 3600;\"\"done\""); } else { m_externalTerminal->start(appName, QStringList() << "-e" << "sh -c \"tty>" + file.fileName() + ";exec<&-;exec>&-;while :;do sleep 3600;done\""); } if (!m_externalTerminal->waitForStarted(500)) { m_lastError = "Can't run terminal: " + appName; m_externalTerminal->terminate(); return false; } for (int i = 0; i < 800; i++) { if (!file.bytesAvailable()) { if (m_externalTerminal->state() == QProcess::NotRunning && m_externalTerminal->exitCode()) { break; } QCoreApplication::processEvents(QEventLoop::AllEvents, 100); usleep(8000); } else { qCDebug(DEBUGGERGDB) << "Received terminal output(tty)"; break; } } usleep(1000); ttySlave = file.readAll().trimmed(); file.close(); if (ttySlave.isEmpty()) { m_lastError = i18n("Can't receive %1 tty/pty. Check that %1 is actually a terminal and that it accepts these arguments: -e sh -c \"tty> %2 ;exec<&-;exec>&-;while :;do sleep 3600;done\"", appName, file.fileName()); } return true; } // ************************************************************************** diff --git a/debuggers/gdb/debugsession.cpp b/debuggers/gdb/debugsession.cpp index 1f337a56ff..896d27bbf4 100644 --- a/debuggers/gdb/debugsession.cpp +++ b/debuggers/gdb/debugsession.cpp @@ -1,278 +1,271 @@ /* * GDB Debugger Support * * Copyright 1999-2001 John Birch * Copyright 2001 by Bernd Gehrmann * Copyright 2006 Vladimir Prus * Copyright 2007 Hamish Rodda * Copyright 2009 Niko Sams * * 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 "debugsession.h" #include "debuglog.h" #include "gdb.h" #include "gdbbreakpointcontroller.h" #include "gdbframestackmodel.h" #include "mi/micommand.h" #include "stty.h" #include "variablecontroller.h" #include #include #include #include #include -#include -#include -#include -#include -#include -#include #include #include #include #include #include #include #include #include using namespace KDevMI::GDB; using namespace KDevMI::MI; using namespace KDevelop; DebugSession::DebugSession() : MIDebugSession() , m_breakpointController(nullptr) , m_variableController(nullptr) , m_frameStackModel(nullptr) { m_breakpointController = new BreakpointController(this); m_variableController = new VariableController(this); m_frameStackModel = new GdbFrameStackModel(this); } DebugSession::~DebugSession() { } BreakpointController *DebugSession::breakpointController() const { return m_breakpointController; } VariableController *DebugSession::variableController() const { return m_variableController; } GdbFrameStackModel *DebugSession::frameStackModel() const { return m_frameStackModel; } GdbDebugger *DebugSession::createDebugger() const { return new GdbDebugger; } void DebugSession::initializeDebugger() { //addCommand(new GDBCommand(GDBMI::EnableTimings, "yes")); addCommand(new CliCommand(MI::GdbShow, "version", this, &DebugSession::handleVersion)); // This makes gdb pump a variable out on one line. addCommand(MI::GdbSet, "width 0"); addCommand(MI::GdbSet, "height 0"); addCommand(MI::SignalHandle, "SIG32 pass nostop noprint"); addCommand(MI::SignalHandle, "SIG41 pass nostop noprint"); addCommand(MI::SignalHandle, "SIG42 pass nostop noprint"); addCommand(MI::SignalHandle, "SIG43 pass nostop noprint"); addCommand(MI::EnablePrettyPrinting); addCommand(MI::GdbSet, "charset UTF-8"); addCommand(MI::GdbSet, "print sevenbit-strings off"); QString fileName = QStandardPaths::locate(QStandardPaths::GenericDataLocation, "kdevgdb/printers/gdbinit"); if (!fileName.isEmpty()) { QFileInfo fileInfo(fileName); QString quotedPrintersPath = fileInfo.dir().path() .replace('\\', "\\\\") .replace('"', "\\\""); addCommand(MI::NonMI, QString("python sys.path.insert(0, \"%0\")").arg(quotedPrintersPath)); addCommand(MI::NonMI, "source " + fileName); } qCDebug(DEBUGGERGDB) << "Initialized GDB"; } void DebugSession::configure(ILaunchConfiguration *cfg) { // Read Configuration values KConfigGroup grp = cfg->config(); bool breakOnStart = grp.readEntry(KDevMI::breakOnStartEntry, false); bool displayStaticMembers = grp.readEntry(KDevMI::staticMembersEntry, false); bool asmDemangle = grp.readEntry(KDevMI::demangleNamesEntry, true); if (breakOnStart) { BreakpointModel* m = ICore::self()->debugController()->breakpointModel(); bool found = false; foreach (Breakpoint *b, m->breakpoints()) { if (b->location() == "main") { found = true; break; } } if (!found) { m->addCodeBreakpoint("main"); } } // Needed so that breakpoint widget has a chance to insert breakpoints. // FIXME: a bit hacky, as we're really not ready for new commands. setDebuggerStateOn(s_dbgBusy); raiseEvent(debugger_ready); if (displayStaticMembers) { addCommand(MI::GdbSet, "print static-members on"); } else { addCommand(MI::GdbSet, "print static-members off"); } if (asmDemangle) { addCommand(MI::GdbSet, "print asm-demangle on"); } else { addCommand(MI::GdbSet, "print asm-demangle off"); } qCDebug(DEBUGGERGDB) << "Per inferior configuration done"; } bool DebugSession::execInferior(ILaunchConfiguration *cfg, const QString &executable) { qCDebug(DEBUGGERGDB) << "Executing inferior"; // debugger specific config configure(cfg); KConfigGroup grp = cfg->config(); QUrl configGdbScript = grp.readEntry(KDevMI::remoteGdbConfigEntry, QUrl()); QUrl runShellScript = grp.readEntry(KDevMI::remoteGdbShellEntry, QUrl()); QUrl runGdbScript = grp.readEntry(KDevMI::remoteGdbRunEntry, QUrl()); // handle remote debug if (configGdbScript.isValid()) { addCommand(MI::NonMI, "source " + KShell::quoteArg(configGdbScript.toLocalFile())); } // FIXME: have a check box option that controls remote debugging if (runShellScript.isValid()) { // Special for remote debug, the remote inferior is started by this shell script QByteArray tty(m_tty->getSlave().toLatin1()); QByteArray options = QByteArray(">") + tty + QByteArray(" 2>&1 <") + tty; QProcess *proc = new QProcess; - KDevelop::restoreSystemEnvironment(proc); QStringList arguments; arguments << "-c" << KShell::quoteArg(runShellScript.toLocalFile()) + ' ' + KShell::quoteArg(executable) + QString::fromLatin1(options); qCDebug(DEBUGGERGDB) << "starting sh" << arguments; proc->start("sh", arguments); //PORTING TODO QProcess::DontCare); } if (runGdbScript.isValid()) { // Special for remote debug, gdb script at run is requested, to connect to remote inferior // Race notice: wait for the remote gdbserver/executable // - but that might be an issue for this script to handle... // Note: script could contain "run" or "continue" // Future: the shell script should be able to pass info (like pid) // to the gdb script... addCommand(new SentinelCommand([this, runGdbScript]() { breakpointController()->initSendBreakpoints(); breakpointController()->setDeleteDuplicateBreakpoints(true); qCDebug(DEBUGGERGDB) << "Running gdb script " << KShell::quoteArg(runGdbScript.toLocalFile()); addCommand(MI::NonMI, "source " + KShell::quoteArg(runGdbScript.toLocalFile()), [this](const MI::ResultRecord&) { breakpointController()->setDeleteDuplicateBreakpoints(false); }, CmdMaybeStartsRunning); raiseEvent(connected_to_program); }, CmdMaybeStartsRunning)); } else { // normal local debugging addCommand(MI::FileExecAndSymbols, KShell::quoteArg(executable), this, &DebugSession::handleFileExecAndSymbols, CmdHandlesError); raiseEvent(connected_to_program); addCommand(new SentinelCommand([this]() { breakpointController()->initSendBreakpoints(); addCommand(MI::ExecRun, QString(), CmdMaybeStartsRunning); }, CmdMaybeStartsRunning)); } return true; } void DebugSession::handleVersion(const QStringList& s) { qCDebug(DEBUGGERGDB) << s.first(); // minimal version is 7.0,0 QRegExp rx("([7-9]+)\\.([0-9]+)(\\.([0-9]+))?"); int idx = rx.indexIn(s.first()); if (idx == -1) { if (!qobject_cast(qApp)) { //for unittest qFatal("You need a graphical application."); } KMessageBox::error( qApp->activeWindow(), i18n("You need gdb 7.0.0 or higher.
" "You are using: %1", s.first()), i18n("gdb error")); stopDebugger(); } } void DebugSession::handleFileExecAndSymbols(const MI::ResultRecord& r) { if (r.reason == "error") { KMessageBox::error( qApp->activeWindow(), i18n("Could not start debugger:
")+ r["msg"].literal(), i18n("Startup error")); stopDebugger(); } } diff --git a/documentation/manpage/tests/test_manpagemodel.cpp b/documentation/manpage/tests/test_manpagemodel.cpp index a88c4f6785..018ec02335 100644 --- a/documentation/manpage/tests/test_manpagemodel.cpp +++ b/documentation/manpage/tests/test_manpagemodel.cpp @@ -1,70 +1,70 @@ /* This file is part of KDevelop Copyright 2014 Kevin Funk 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. */ #include "../manpagedocumentation.h" #include "../manpagemodel.h" #include #include #include #include class TestManPageModel : public QObject { Q_OBJECT private Q_SLOTS: void testModel(); void testDocumentation(); }; void TestManPageModel::testModel() { ManPageModel model; QSignalSpy spy(&model, SIGNAL(manPagesLoaded())); spy.wait(); if (model.isLoaded()) { QVERIFY(model.rowCount() > 0); new ModelTest(&model); } else { QCOMPARE(model.rowCount(), 0); } } void TestManPageModel::testDocumentation() { - ManPageDocumentation documentation("dlopen", QUrl("man: (3)/dlopen")); + ManPageDocumentation documentation("dlopen", QUrl("man: (3)/dlmopen")); QSignalSpy spy(&documentation, SIGNAL(descriptionChanged())); QVERIFY(spy.wait()); const QString description = documentation.description(); if (!description.isEmpty()) { qDebug() << description; // check that we've found the correct page by checking some references QVERIFY(description.contains("dlclose")); QVERIFY(description.contains("dlerror")); QVERIFY(description.contains("dlopen")); } } QTEST_MAIN(TestManPageModel) #include "test_manpagemodel.moc" diff --git a/documentation/qthelp/CMakeLists.txt b/documentation/qthelp/CMakeLists.txt index 23641cf26c..788fd0cf56 100644 --- a/documentation/qthelp/CMakeLists.txt +++ b/documentation/qthelp/CMakeLists.txt @@ -1,25 +1,25 @@ add_definitions(-DTRANSLATION_DOMAIN=\"kdevqthelp\") set(kdevqthelp_SRCS qthelpplugin.cpp qthelpproviderabstract.cpp qthelpprovider.cpp qthelpdocumentation.cpp qthelpqtdoc.cpp qthelp_config_shared.cpp debug.cpp qthelpconfig.cpp # Configuration module for QtHelp plugin ) ki18n_wrap_ui(kdevqthelp_SRCS qthelpconfig.ui qthelpconfigeditdialog.ui ) qt5_add_resources(kdevqthelp_SRCS kdevqthelp.qrc) kdevplatform_add_plugin(kdevqthelp JSON kdevqthelp.json SOURCES ${kdevqthelp_SRCS}) target_link_libraries(kdevqthelp KF5::KCMUtils KF5::I18n KF5::KIOWidgets KF5::TextEditor KF5::IconThemes Qt5::Help Qt5::WebKitWidgets KF5::NewStuff - KDev::Language KDev::Documentation KDev::Interfaces KDev::Util) + KDev::Language KDev::Documentation KDev::Interfaces) add_subdirectory(tests) diff --git a/documentation/qthelp/qthelpqtdoc.cpp b/documentation/qthelp/qthelpqtdoc.cpp index c4b3b477ab..9cb626d18f 100644 --- a/documentation/qthelp/qthelpqtdoc.cpp +++ b/documentation/qthelp/qthelpqtdoc.cpp @@ -1,146 +1,143 @@ /* This file is part of KDevelop Copyright 2009 Aleix Pol Copyright 2009 David Nolden Copyright 2010 Benjamin Port Copyright 2016 Andreas Cord-Landwehr 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. */ #include "qthelpqtdoc.h" #include "debug.h" -#include - #include #include #include #include #include #include namespace { QString qmakeCandidate() { // return the first qmake executable we can find const QStringList candidates = {"qmake", "qmake-qt4", "qmake-qt5"}; auto it = std::find_if(candidates.constBegin(), candidates.constEnd(), [](const QString& candidate) { return !QStandardPaths::findExecutable(candidate).isEmpty(); }); return it != candidates.constEnd() ? *it : QString(); } } QtHelpQtDoc::QtHelpQtDoc(QObject *parent, const QVariantList &args) : QtHelpProviderAbstract(parent, "qthelpcollection.qhc", args) , m_path(QString()) { Q_UNUSED(args); registerDocumentations(); } void QtHelpQtDoc::registerDocumentations() { const QString qmake = qmakeCandidate(); if (!qmake.isEmpty()) { QProcess *p = new QProcess; - KDevelop::restoreSystemEnvironment(p); p->setProcessChannelMode(QProcess::MergedChannels); p->setProgram(qmake); p->setArguments(QStringList(QStringLiteral("-query QT_INSTALL_DOCS"))); p->start(); connect(p, static_cast(&QProcess::finished), this, &QtHelpQtDoc::lookupDone); } } void QtHelpQtDoc::lookupDone(int code) { if(code==0) { QProcess* p = qobject_cast(sender()); m_path = QDir::fromNativeSeparators(QString::fromLatin1(p->readAllStandardOutput().trimmed())); qCDebug(QTHELP) << "Detected doc path:" << m_path; } sender()->deleteLater(); } void QtHelpQtDoc::loadDocumentation() { if(m_path.isEmpty()) { return; } QStringList files = qchFiles(); if(files.isEmpty()) { qCWarning(QTHELP) << "could not find QCH file in directory" << m_path; return; } foreach(const QString &fileName, files) { QString fileNamespace = QHelpEngineCore::namespaceName(fileName); if (!fileNamespace.isEmpty() && !m_engine.registeredDocumentations().contains(fileNamespace)) { qCDebug(QTHELP) << "loading doc" << fileName << fileNamespace; if(!m_engine.registerDocumentation(fileName)) qCCritical(QTHELP) << "error >> " << fileName << m_engine.error(); } } } void QtHelpQtDoc::unloadDocumentation() { foreach(const QString &fileName, qchFiles()) { QString fileNamespace = QHelpEngineCore::namespaceName(fileName); if(!fileName.isEmpty() && m_engine.registeredDocumentations().contains(fileNamespace)) { m_engine.unregisterDocumentation(fileName); } } } QStringList QtHelpQtDoc::qchFiles() const { QStringList files; QVector paths; // test directories paths << m_path << m_path+"/qch/"; foreach (const auto &path, paths) { QDir d(path); if(path.isEmpty() || !d.exists()) { continue; } foreach(const auto& file, d.entryInfoList(QDir::Files)) { files << file.absoluteFilePath(); } } if (files.isEmpty()) { qCDebug(QTHELP) << "no QCH file found at all"; } return files; } QIcon QtHelpQtDoc::icon() const { return QIcon::fromTheme("qtlogo"); } QString QtHelpQtDoc::name() const { return i18n("QtHelp"); } diff --git a/kdeintegration/executeplasmoid/plasmoidexecutionconfig.cpp b/kdeintegration/executeplasmoid/plasmoidexecutionconfig.cpp index 9300d5f013..dd145c35cd 100644 --- a/kdeintegration/executeplasmoid/plasmoidexecutionconfig.cpp +++ b/kdeintegration/executeplasmoid/plasmoidexecutionconfig.cpp @@ -1,331 +1,328 @@ /* This file is part of KDevelop Copyright 2009 Andreas Pakulat Copyright 2009 Niko Sams 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. */ #include "plasmoidexecutionconfig.h" #include "plasmoidexecutionjob.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #include #include #include class la; Q_DECLARE_METATYPE(KDevelop::IProject*); QIcon PlasmoidExecutionConfig::icon() const { return QIcon::fromTheme("system-run"); } QStringList readProcess(QProcess* p) { QStringList ret; while(!p->atEnd()) { QByteArray line = p->readLine(); int nameEnd=line.indexOf(' '); if(nameEnd>0) { ret += line.left(nameEnd); } } return ret; } PlasmoidExecutionConfig::PlasmoidExecutionConfig( QWidget* parent ) : LaunchConfigurationPage( parent ) { setupUi(this); connect( identifier->lineEdit(), &QLineEdit::textEdited, this, &PlasmoidExecutionConfig::changed ); QProcess pPlasmoids; - KDevelop::restoreSystemEnvironment(&pPlasmoids); pPlasmoids.start("plasmoidviewer", QStringList("--list"), QIODevice::ReadOnly); QProcess pThemes; - KDevelop::restoreSystemEnvironment(&pThemes); pThemes.start("plasmoidviewer", QStringList("--list-themes"), QIODevice::ReadOnly); pThemes.waitForFinished(); pPlasmoids.waitForFinished(); foreach(const QString& plasmoid, readProcess(&pPlasmoids)) { identifier->addItem(plasmoid); } themes->addItem(QString()); foreach(const QString& theme, readProcess(&pThemes)) { themes->addItem(theme); } connect( dependencies, &KDevelop::DependenciesWidget::changed, this, &PlasmoidExecutionConfig::changed ); } void PlasmoidExecutionConfig::saveToConfiguration( KConfigGroup cfg, KDevelop::IProject* project ) const { Q_UNUSED( project ); cfg.writeEntry("PlasmoidIdentifier", identifier->lineEdit()->text()); QStringList args; args += "--formfactor"; args += formFactor->currentText(); if(!themes->currentText().isEmpty()) { args += "--theme"; args += themes->currentText(); } cfg.writeEntry("Arguments", args); QVariantList deps = dependencies->dependencies(); cfg.writeEntry( "Dependencies", KDevelop::qvariantToString( QVariant( deps ) ) ); } void PlasmoidExecutionConfig::loadFromConfiguration(const KConfigGroup& cfg, KDevelop::IProject* ) { bool b = blockSignals( true ); identifier->lineEdit()->setText(cfg.readEntry("PlasmoidIdentifier", "")); blockSignals( b ); QStringList arguments = cfg.readEntry("Arguments", QStringList()); int idxFormFactor = arguments.indexOf("--formfactor")+1; if(idxFormFactor>0) formFactor->setCurrentIndex(formFactor->findText(arguments[idxFormFactor])); int idxTheme = arguments.indexOf("--theme")+1; if(idxTheme>0) themes->setCurrentIndex(themes->findText(arguments[idxTheme])); dependencies->setDependencies( KDevelop::stringToQVariant( cfg.readEntry( "Dependencies", QString() ) ).toList()); } QString PlasmoidExecutionConfig::title() const { return i18n("Configure Plasmoid Execution"); } QList< KDevelop::LaunchConfigurationPageFactory* > PlasmoidLauncher::configPages() const { return QList(); } QString PlasmoidLauncher::description() const { return i18n("Display a plasmoid"); } QString PlasmoidLauncher::id() { return "PlasmoidLauncher"; } QString PlasmoidLauncher::name() const { return i18n("Plasmoid Launcher"); } PlasmoidLauncher::PlasmoidLauncher(ExecutePlasmoidPlugin* plugin) : m_plugin(plugin) { } KJob* PlasmoidLauncher::start(const QString& launchMode, KDevelop::ILaunchConfiguration* cfg) { Q_ASSERT(cfg); if( !cfg ) { return 0; } if( launchMode == "execute" ) { KJob* depsJob = dependencies(cfg); QList jobs; if(depsJob) jobs << depsJob; jobs << new PlasmoidExecutionJob(m_plugin, cfg); return new KDevelop::ExecuteCompositeJob( KDevelop::ICore::self()->runController(), jobs ); } qWarning() << "Unknown launch mode " << launchMode << "for config:" << cfg->name(); return 0; } KJob* PlasmoidLauncher::calculateDependencies(KDevelop::ILaunchConfiguration* cfg) { QVariantList deps = KDevelop::stringToQVariant( cfg->config().readEntry( "Dependencies", QString() ) ).toList(); if( !deps.isEmpty() ) { KDevelop::ProjectModel* model = KDevelop::ICore::self()->projectController()->projectModel(); QList items; foreach( const QVariant& dep, deps ) { KDevelop::ProjectBaseItem* item = model->itemFromIndex( model->pathToIndex( dep.toStringList() ) ); if( item ) { items << item; } else { KMessageBox::error(KDevelop::ICore::self()->uiController()->activeMainWindow(), i18n("Couldn't resolve the dependency: %1", dep.toString())); } } KDevelop::BuilderJob* job = new KDevelop::BuilderJob; job->addItems( KDevelop::BuilderJob::Install, items ); job->updateJobName(); return job; } return 0; } KJob* PlasmoidLauncher::dependencies(KDevelop::ILaunchConfiguration* cfg) { return calculateDependencies(cfg); } QStringList PlasmoidLauncher::supportedModes() const { return QStringList() << "execute"; } KDevelop::LaunchConfigurationPage* PlasmoidPageFactory::createWidget(QWidget* parent) { return new PlasmoidExecutionConfig( parent ); } PlasmoidPageFactory::PlasmoidPageFactory() {} PlasmoidExecutionConfigType::PlasmoidExecutionConfigType() { factoryList.append( new PlasmoidPageFactory ); } PlasmoidExecutionConfigType::~PlasmoidExecutionConfigType() { qDeleteAll(factoryList); factoryList.clear(); } QString PlasmoidExecutionConfigType::name() const { return i18n("Plasmoid Launcher"); } QList PlasmoidExecutionConfigType::configPages() const { return factoryList; } QString PlasmoidExecutionConfigType::typeId() { return "PlasmoidLauncherType"; } QIcon PlasmoidExecutionConfigType::icon() const { return QIcon::fromTheme("plasma"); } static bool canLaunchMetadataFile(const KDevelop::Path &path) { KConfig cfg(path.toLocalFile(), KConfig::SimpleConfig); KConfigGroup group(&cfg, "Desktop Entry"); QStringList services = group.readEntry("ServiceTypes", group.readEntry("X-KDE-ServiceTypes", QStringList())); return services.contains("Plasma/Applet"); } //don't bother, nobody uses this interface bool PlasmoidExecutionConfigType::canLaunch(const QUrl& ) const { return false; } bool PlasmoidExecutionConfigType::canLaunch(KDevelop::ProjectBaseItem* item) const { KDevelop::ProjectFolderItem* folder = item->folder(); if(folder && folder->hasFileOrFolder("metadata.desktop")) { return canLaunchMetadataFile(KDevelop::Path(folder->path(), "metadata.desktop")); } return false; } void PlasmoidExecutionConfigType::configureLaunchFromItem(KConfigGroup config, KDevelop::ProjectBaseItem* item) const { config.writeEntry("PlasmoidIdentifier", item->path().toUrl().toLocalFile()); } void PlasmoidExecutionConfigType::configureLaunchFromCmdLineArguments(KConfigGroup /*config*/, const QStringList &/*args*/) const {} QMenu* PlasmoidExecutionConfigType::launcherSuggestions() { QList found; QList projects = KDevelop::ICore::self()->projectController()->projects(); foreach(KDevelop::IProject* p, projects) { QSet files = p->fileSet(); foreach(const KDevelop::IndexedString& file, files) { KDevelop::Path path(file.str()); if (path.lastPathSegment() == "metadata.desktop" && canLaunchMetadataFile(path)) { path = path.parent(); QString relUrl = p->path().relativePath(path); QAction* action = new QAction(relUrl, this); action->setProperty("url", relUrl); action->setProperty("project", qVariantFromValue(p)); connect(action, &QAction::triggered, this, &PlasmoidExecutionConfigType::suggestionTriggered); found.append(action); } } } QMenu *m = 0; if(!found.isEmpty()) { m = new QMenu(i18n("Plasmoids")); m->addActions(found); } return m; } void PlasmoidExecutionConfigType::suggestionTriggered() { QAction* action = qobject_cast(sender()); KDevelop::IProject* p = action->property("project").value(); QString relUrl = action->property("url").toString(); KDevelop::ILauncher* launcherInstance = launchers().at( 0 ); QPair launcher = qMakePair( launcherInstance->supportedModes().at(0), launcherInstance->id() ); QString name = relUrl.mid(relUrl.lastIndexOf('/')+1); KDevelop::ILaunchConfiguration* config = KDevelop::ICore::self()->runController()->createLaunchConfiguration(this, launcher, p, name); KConfigGroup cfg = config->config(); cfg.writeEntry("PlasmoidIdentifier", relUrl); emit signalAddLaunchConfiguration(config); } diff --git a/languages/clang/codegen/adaptsignatureassistant.cpp b/languages/clang/codegen/adaptsignatureassistant.cpp index 526643a39a..8074512b60 100644 --- a/languages/clang/codegen/adaptsignatureassistant.cpp +++ b/languages/clang/codegen/adaptsignatureassistant.cpp @@ -1,311 +1,310 @@ /* Copyright 2009 David Nolden Copyright 2014 Kevin Funk This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License version 2 as published by the Free Software Foundation. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "adaptsignatureassistant.h" #include #include #include #include #include #include #include #include #include #include "../util/clangdebug.h" using namespace KDevelop; namespace { Declaration *getDeclarationAtCursor(const KTextEditor::Cursor &cursor, const QUrl &documentUrl) { ENSURE_CHAIN_READ_LOCKED ReferencedTopDUContext top(DUChainUtils::standardContextForUrl(documentUrl)); if (!top) { clangDebug() << "no context found for document" << documentUrl; return nullptr; } const auto *context = top->findContextAt(top->transformToLocalRevision(cursor), true); return context->type() == DUContext::Function ? context->owner() : nullptr; } bool isConstructor(const Declaration *functionDecl) { auto classFun = dynamic_cast(functionDecl); return classFun && classFun->isConstructor(); } Signature getDeclarationSignature(const Declaration *functionDecl, const DUContext *functionCtxt, bool includeDefaults) { ENSURE_CHAIN_READ_LOCKED int pos = 0; Signature signature; const AbstractFunctionDeclaration* abstractFunDecl = dynamic_cast(functionDecl); foreach(Declaration * parameter, functionCtxt->localDeclarations()) { signature.defaultParams << (includeDefaults ? abstractFunDecl->defaultParameterForArgument(pos).str() : QString()); signature.parameters << qMakePair(parameter->indexedType(), parameter->identifier().identifier().str()); ++pos; } signature.isConst = functionDecl->abstractType() && functionDecl->abstractType()->modifiers() & AbstractType::ConstModifier; if (!isConstructor(functionDecl)) { if (auto funType = functionDecl->type()) { signature.returnType = IndexedType(funType->returnType()); } } return signature; } } AdaptSignatureAssistant::AdaptSignatureAssistant(ILanguageSupport* supportedLanguage) : StaticAssistant(supportedLanguage) { connect(DUChain::self(), &DUChain::updateReady, this, &AdaptSignatureAssistant::updateReady); } QString AdaptSignatureAssistant::title() const { return tr("Adapt Signature"); } void AdaptSignatureAssistant::reset() { doHide(); clearActions(); m_editingDefinition = {}; m_declarationName = {}; m_otherSideId = DeclarationId(); m_otherSideTopContext = {}; m_otherSideContext = {}; m_oldSignature = {}; m_document = {}; m_view.clear(); } void AdaptSignatureAssistant::textChanged(KTextEditor::View* view, const KTextEditor::Range& invocationRange, const QString& removedText) { reset(); m_view = view; KTextEditor::Range sigAssistRange = invocationRange; if (!removedText.isEmpty()) { sigAssistRange.setRange(sigAssistRange.start(), sigAssistRange.start()); } m_document = view->document()->url(); DUChainReadLocker lock(DUChain::lock(), 300); if (!lock.locked()) { clangDebug() << "failed to lock duchain in time"; return; } KTextEditor::Range simpleInvocationRange = KTextEditor::Range(sigAssistRange); Declaration* funDecl = getDeclarationAtCursor(simpleInvocationRange.start(), m_document); if (!funDecl || !funDecl->type()) { clangDebug() << "No function at cursor"; return; } /* TODO: Port? if(QtFunctionDeclaration* classFun = dynamic_cast(funDecl)) { if (classFun->isSignal()) { // do not offer to change signature of a signal, as the implementation will be generated by moc return; } } */ Declaration* otherSide = 0; FunctionDefinition* definition = dynamic_cast(funDecl); if (definition) { m_editingDefinition = true; otherSide = definition->declaration(); } else if ((definition = FunctionDefinition::definition(funDecl))) { m_editingDefinition = false; otherSide = definition; } if (!otherSide) { clangDebug() << "no other side for signature found"; return; } m_otherSideContext = DUContextPointer(DUChainUtils::getFunctionContext(otherSide)); if (!m_otherSideContext) { clangDebug() << "no context for other side found"; return; } m_declarationName = funDecl->identifier(); m_otherSideId = otherSide->id(); m_otherSideTopContext = ReferencedTopDUContext(otherSide->topContext()); m_oldSignature = getDeclarationSignature(otherSide, m_otherSideContext.data(), true); //Schedule an update, to make sure the ranges match DUChain::self()->updateContextForUrl(m_otherSideTopContext->url(), TopDUContext::AllDeclarationsAndContexts); } bool AdaptSignatureAssistant::isUseful() const { return !m_declarationName.isEmpty() && m_otherSideId.isValid(); } bool AdaptSignatureAssistant::getSignatureChanges(const Signature& newSignature, QList& oldPositions) const { bool changed = false; for (int i = 0; i < newSignature.parameters.size(); ++i) { oldPositions.append(-1); } for (int curNewParam = newSignature.parameters.size() - 1; curNewParam >= 0; --curNewParam) { int foundAt = -1; for (int curOldParam = m_oldSignature.parameters.size() - 1; curOldParam >= 0; --curOldParam) { if (newSignature.parameters[curNewParam].first != m_oldSignature.parameters[curOldParam].first) { continue; //Different type == different parameters } if (newSignature.parameters[curNewParam].second == m_oldSignature.parameters[curOldParam].second || curOldParam == curNewParam) { //given the same type and either the same position or the same name, it's (probably) the same argument foundAt = curOldParam; if (newSignature.parameters[curNewParam].second != m_oldSignature.parameters[curOldParam].second || curOldParam != curNewParam) { changed = true; //Either the name changed at this position, or position of this name has changed } if (newSignature.parameters[curNewParam].second == m_oldSignature.parameters[curOldParam].second) { break; //Found an argument with the same name and type, no need to look further } //else: position/type match, but name match will trump, allowing: (int i=0, int j=1) => (int j=1, int i=0) } } if (foundAt < 0) { changed = true; } oldPositions[curNewParam] = foundAt; } if (newSignature.parameters.size() != m_oldSignature.parameters.size()) { changed = true; } if (newSignature.isConst != m_oldSignature.isConst) { changed = true; } if (newSignature.returnType != m_oldSignature.returnType) { changed = true; } return changed; } void AdaptSignatureAssistant::setDefaultParams(Signature& newSignature, const QList& oldPositions) const { bool hadDefaultParam = false; for (int i = 0; i < newSignature.defaultParams.size(); ++i) { const auto oldPos = oldPositions[i]; if (oldPos == -1) { // default-initialize new argument if we encountered a previous default param if (hadDefaultParam) { newSignature.defaultParams[i] = QStringLiteral("{} /* TODO */"); } } else { newSignature.defaultParams[i] = m_oldSignature.defaultParams[oldPos]; hadDefaultParam = hadDefaultParam || !newSignature.defaultParams[i].isEmpty(); } } } QList AdaptSignatureAssistant::getRenameActions(const Signature &newSignature, const QList &oldPositions) const { Q_ASSERT(DUChain::lock()->currentThreadHasReadLock()); QList renameActions; - qDebug() << "called" << oldPositions << m_otherSideContext; if (!m_otherSideContext) { return renameActions; } for (int i = newSignature.parameters.size() - 1; i >= 0; --i) { if (oldPositions[i] == -1) { continue; //new parameter } Declaration *renamedDecl = m_otherSideContext->localDeclarations()[oldPositions[i]]; if (newSignature.parameters[i].second != m_oldSignature.parameters[oldPositions[i]].second) { QMap > uses = renamedDecl->uses(); if (!uses.isEmpty()) { renameActions << new RenameAction(renamedDecl->identifier(), newSignature.parameters[i].second, RevisionedFileRanges::convert(uses)); } } } return renameActions; } void AdaptSignatureAssistant::updateReady(const KDevelop::IndexedString& document, const KDevelop::ReferencedTopDUContext& /*context*/) { if (document.toUrl() != m_document || !m_view) { return; } clearActions(); DUChainReadLocker lock; Declaration *functionDecl = getDeclarationAtCursor(KTextEditor::Cursor(m_view.data()->cursorPosition()), m_document); if (!functionDecl || functionDecl->identifier() != m_declarationName) { clangDebug() << "No function found at" << m_document << m_view.data()->cursorPosition(); return; } DUContext *functionCtxt = DUChainUtils::getFunctionContext(functionDecl); if (!functionCtxt) { clangDebug() << "No function context found for" << functionDecl->toString(); return; } #if 0 // TODO: Port if (QtFunctionDeclaration * classFun = dynamic_cast(functionDecl)) { if (classFun->isSignal()) { // do not offer to change signature of a signal, as the implementation will be generated by moc return; } } #endif //ParseJob having finished, get the signature that was modified Signature newSignature = getDeclarationSignature(functionDecl, functionCtxt, false); //Check for changes between m_oldSignature and newSignature, use oldPositions to store old<->new param index mapping QList oldPositions; if (!getSignatureChanges(newSignature, oldPositions)) { reset(); clangDebug() << "no changes to signature"; return; //No changes to signature } QList renameActions; if (m_editingDefinition) { setDefaultParams(newSignature, oldPositions); //restore default parameters before updating the declarations } else { renameActions = getRenameActions(newSignature, oldPositions); //rename as needed when updating the definition } IAssistantAction::Ptr action(new AdaptSignatureAction(m_otherSideId, m_otherSideTopContext, m_oldSignature, newSignature, m_editingDefinition, renameActions)); connect(action.data(), &IAssistantAction::executed, this, &AdaptSignatureAssistant::reset); addAction(action); emit actionsChanged(); } #include "moc_adaptsignatureassistant.cpp" diff --git a/languages/clang/duchain/clanghelpers.cpp b/languages/clang/duchain/clanghelpers.cpp index 976da8a821..e07daee9fc 100644 --- a/languages/clang/duchain/clanghelpers.cpp +++ b/languages/clang/duchain/clanghelpers.cpp @@ -1,305 +1,304 @@ /* * Copyright 2014 Olivier de Gaalon * Copyright 2014 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) 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 "clanghelpers.h" #include #include #include #include #include #include "builder.h" #include "parsesession.h" #include "clangparsingenvironmentfile.h" #include "clangindex.h" #include "clangducontext.h" #include "util/clangtypes.h" #include using namespace KDevelop; namespace { CXChildVisitResult visitCursor(CXCursor cursor, CXCursor, CXClientData data) { if (cursor.kind != CXCursor_InclusionDirective) { return CXChildVisit_Continue; } auto imports = static_cast(data); CXFile file = clang_getIncludedFile(cursor); if(!file){ return CXChildVisit_Continue; } CXSourceLocation location = clang_getCursorLocation(cursor); CXFile parentFile; uint line, column; clang_getFileLocation(location, &parentFile, &line, &column, nullptr); foreach (const auto& import, imports->values(parentFile)) { // clang_getInclusions doesn't include the same import twice, so we shouldn't do it too. if (import.file == file) { return CXChildVisit_Continue; } } imports->insert(parentFile, {file, CursorInRevision(line-1, column-1)}); return CXChildVisit_Recurse; } ReferencedTopDUContext createTopContext(const IndexedString& path, const ClangParsingEnvironment& environment) { ClangParsingEnvironmentFile* file = new ClangParsingEnvironmentFile(path, environment); ReferencedTopDUContext context = new ClangTopDUContext(path, RangeInRevision(0, 0, INT_MAX, INT_MAX), file); DUChain::self()->addDocumentChain(context); context->updateImportsCache(); return context; } } Imports ClangHelpers::tuImports(CXTranslationUnit tu) { Imports imports; // Intentionally don't use clang_getInclusions here, as it skips already visited inclusions // which makes TestDUChain::testNestedImports fail CXCursor tuCursor = clang_getTranslationUnitCursor(tu); clang_visitChildren(tuCursor, &visitCursor, &imports); return imports; } ReferencedTopDUContext ClangHelpers::buildDUChain(CXFile file, const Imports& imports, const ParseSession& session, TopDUContext::Features features, IncludeFileContexts& includedFiles, ClangIndex* index, const std::function& abortFunction) { if (includedFiles.contains(file)) { return {}; } if (abortFunction && abortFunction()) { return {}; } // prevent recursion includedFiles.insert(file, {}); // ensure DUChain for imports are build properly foreach(const auto& import, imports.values(file)) { buildDUChain(import.file, imports, session, features, includedFiles, index, abortFunction); } const IndexedString path(QDir(ClangString(clang_getFileName(file)).toString()).canonicalPath()); if (path.isEmpty()) { // may happen when the file gets removed before the job is run return {}; } const auto& environment = session.environment(); bool update = false; UrlParseLock urlLock(path); ReferencedTopDUContext context; { DUChainWriteLocker lock; context = DUChain::self()->chainForDocument(path, &environment); if (!context) { context = ::createTopContext(path, environment); } else { update = true; } includedFiles.insert(file, context); if (update) { auto envFile = ClangParsingEnvironmentFile::Ptr(dynamic_cast(context->parsingEnvironmentFile().data())); Q_ASSERT(envFile); if (!envFile) return context; /* NOTE: When we are here, then either the translation unit or one of its headers was changed. * Thus we must always update the translation unit to propagate the change(s). * See also: https://bugs.kde.org/show_bug.cgi?id=356327 * This assumes that headers are independent, we may need to improve that in the future * and also update header files more often when other files included therein got updated. */ if (path != environment.translationUnitUrl() && !envFile->needsUpdate(&environment) && envFile->featuresSatisfied(features)) { return context; } else { //TODO: don't attempt to update if this environment is worse quality than the outdated one if (index && envFile->environmentQuality() < environment.quality()) { index->pinTranslationUnitForUrl(environment.translationUnitUrl(), path); } envFile->setEnvironment(environment); envFile->setModificationRevision(ModificationRevision::revisionForFile(context->url())); } context->clearImportedParentContexts(); } context->setFeatures(features); foreach(const auto& import, imports.values(file)) { - Q_ASSERT(includedFiles.contains(import.file)); auto ctx = includedFiles.value(import.file); if (!ctx) { // happens for cyclic imports continue; } context->addImportedParentContext(ctx, import.location); } context->updateImportsCache(); } const auto problems = session.problemsForFile(file); { DUChainWriteLocker lock; context->setProblems(problems); } Builder::visit(session.unit(), file, includedFiles, update); DUChain::self()->emitUpdateReady(path, context); return context; } DeclarationPointer ClangHelpers::findDeclaration(CXSourceLocation location, const ReferencedTopDUContext& top) { if (!top) { // may happen for cyclic includes return {}; } auto cursor = CursorInRevision(ClangLocation(location)); DUChainReadLocker lock; Q_ASSERT(top); if (DUContext *local = top->findContextAt(cursor)) { if (local->owner() && local->owner()->range().contains(cursor)) { return DeclarationPointer(local->owner()); } return DeclarationPointer(local->findDeclarationAt(cursor)); } return {}; } DeclarationPointer ClangHelpers::findDeclaration(CXCursor cursor, const IncludeFileContexts& includes) { auto location = clang_getCursorLocation(cursor); CXFile file = nullptr; clang_getFileLocation(location, &file, nullptr, nullptr, nullptr); if (!file) { return {}; } return findDeclaration(location, includes.value(file)); } DeclarationPointer ClangHelpers::findDeclaration(CXType type, const IncludeFileContexts& includes) { CXCursor cursor = clang_getTypeDeclaration(type); return findDeclaration(cursor, includes); } DeclarationPointer ClangHelpers::findForwardDeclaration(CXType type, DUContext* context, CXCursor cursor) { if(type.kind != CXType_Record && type.kind != CXType_ObjCInterface && type.kind != CXType_ObjCClass){ return {}; } auto qualifiedIdentifier = QualifiedIdentifier(ClangString(clang_getTypeSpelling(type)).toString()); DUChainReadLocker lock; auto decls = context->findDeclarations(qualifiedIdentifier, CursorInRevision(ClangLocation(clang_getCursorLocation(cursor))) ); foreach (auto decl, decls) { if (decl->isForwardDeclaration()) { return DeclarationPointer(decl); } } return {}; } RangeInRevision ClangHelpers::cursorSpellingNameRange(CXCursor cursor, const Identifier& id) { auto range = ClangRange(clang_Cursor_getSpellingNameRange(cursor, 0, 0)).toRangeInRevision(); #if CINDEX_VERSION_MINOR < 29 auto kind = clang_getCursorKind(cursor); // Clang used to report invalid ranges for destructors and methods like 'operator=' if (kind == CXCursor_Destructor || kind == CXCursor_CXXMethod) { range.end.column = range.start.column + id.toString().length(); } #endif Q_UNUSED(id); return range; } QStringList ClangHelpers::headerExtensions() { static const QStringList headerExtensions = { QStringLiteral("h"), QStringLiteral("H"), QStringLiteral("hh"), QStringLiteral("hxx"), QStringLiteral("hpp"), QStringLiteral("tlh"), QStringLiteral("h++"), }; return headerExtensions; } QStringList ClangHelpers::sourceExtensions() { static const QStringList sourceExtensions = { QStringLiteral("c"), QStringLiteral("cc"), QStringLiteral("cpp"), QStringLiteral("c++"), QStringLiteral("cxx"), QStringLiteral("C"), QStringLiteral("m"), QStringLiteral("mm"), QStringLiteral("M"), QStringLiteral("inl"), QStringLiteral("_impl.h"), }; return sourceExtensions; } bool ClangHelpers::isSource(const QString& path) { const auto& extensions = sourceExtensions(); return std::any_of(extensions.constBegin(), extensions.constEnd(), [&](const QString& ext) { return path.endsWith(ext); }); } bool ClangHelpers::isHeader(const QString& path) { const auto& extensions = headerExtensions(); return std::any_of(extensions.constBegin(), extensions.constEnd(), [&](const QString& ext) { return path.endsWith(ext); }); } diff --git a/languages/clang/duchain/clangproblem.cpp b/languages/clang/duchain/clangproblem.cpp index 38377c63e0..2c6a334891 100644 --- a/languages/clang/duchain/clangproblem.cpp +++ b/languages/clang/duchain/clangproblem.cpp @@ -1,262 +1,268 @@ /* * Copyright 2014 Kevin Funk * * 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 "clangproblem.h" +#include +#include #include "util/clangtypes.h" #include "util/clangdebug.h" #include #include #include using namespace KDevelop; namespace { IProblem::Severity diagnosticSeverityToSeverity(CXDiagnosticSeverity severity, const QString& optionName) { switch (severity) { case CXDiagnostic_Fatal: case CXDiagnostic_Error: return IProblem::Error; case CXDiagnostic_Warning: if (optionName.startsWith(QLatin1String("-Wunused-"))) { return IProblem::Hint; } return IProblem::Warning; break; default: return IProblem::Hint; } } /** * Clang diagnostic messages always start with a lowercase character * * @return Prettified version, starting with uppercase character */ inline QString prettyDiagnosticSpelling(const QString& str) { QString ret = str; if (ret.isEmpty()) { return {}; } ret[0] = ret[0].toUpper(); return ret; } ClangFixits fixitsForDiagnostic(CXDiagnostic diagnostic) { ClangFixits fixits; auto numFixits = clang_getDiagnosticNumFixIts(diagnostic); for (uint i = 0; i < numFixits; ++i) { CXSourceRange range; const QString replacementText = ClangString(clang_getDiagnosticFixIt(diagnostic, i, &range)).toString(); + auto location = ClangString(clang_formatDiagnostic(diagnostic, CXDiagnostic_DisplaySourceLocation)).toString(); + const auto docRange = ClangRange(range).toDocumentRange(); + auto doc = KDevelop::ICore::self()->documentController()->documentForUrl(docRange.document.toUrl()); + const QString original = doc ? doc->text(docRange) : QString{}; - // TODO: Apparently there's no way to find out the raw text via the C API given a source range - // Could be useful to pass that into ClangFixit to be sure to replace the correct text - // cf. DocumentChangeSet.m_oldText - fixits << ClangFixit{replacementText, ClangRange(range).toDocumentRange(), QString()}; + fixits << ClangFixit{replacementText, docRange, QString(), original}; } return fixits; } } QDebug operator<<(QDebug debug, const ClangFixit& fixit) { debug.nospace() << "ClangFixit[" << "replacementText=" << fixit.replacementText << ", range=" << fixit.range << ", description=" << fixit.description << "]"; return debug; } ClangProblem::ClangProblem(CXDiagnostic diagnostic, CXTranslationUnit unit) { const QString diagnosticOption = ClangString(clang_getDiagnosticOption(diagnostic, nullptr)).toString(); auto severity = diagnosticSeverityToSeverity(clang_getDiagnosticSeverity(diagnostic), diagnosticOption); setSeverity(severity); QString description = ClangString(clang_getDiagnosticSpelling(diagnostic)).toString(); if (!diagnosticOption.isEmpty()) { description.append(QStringLiteral(" [%1]").arg(diagnosticOption)); } setDescription(prettyDiagnosticSpelling(description)); ClangLocation location(clang_getDiagnosticLocation(diagnostic)); CXFile diagnosticFile; clang_getFileLocation(location, &diagnosticFile, nullptr, nullptr, nullptr); const ClangString fileName(clang_getFileName(diagnosticFile)); DocumentRange docRange(fileName.toIndexed(), KTextEditor::Range(location, location)); const uint numRanges = clang_getDiagnosticNumRanges(diagnostic); for (uint i = 0; i < numRanges; ++i) { auto range = ClangRange(clang_getDiagnosticRange(diagnostic, i)).toRange(); if(!range.isValid()){ continue; } if (range.start() < docRange.start()) { docRange.setStart(range.start()); } if (range.end() > docRange.end()) { docRange.setEnd(range.end()); } } if (docRange.isEmpty()) { // try to find a bigger range for the given location by using the token at the given location CXFile file = nullptr; unsigned line = 0; unsigned column = 0; clang_getExpansionLocation(location, &file, &line, &column, nullptr); // just skip ahead some characters, hoping that it's sufficient to encompass // a token we can use for building the range auto nextLocation = clang_getLocation(unit, file, line, column + 100); auto rangeToTokenize = clang_getRange(location, nextLocation); const ClangTokens tokens(unit, rangeToTokenize); if (tokens.size()) { docRange.setRange(ClangRange(clang_getTokenExtent(unit, tokens.at(0))).toRange()); } } setFixits(fixitsForDiagnostic(diagnostic)); setFinalLocation(docRange); setSource(IProblem::SemanticAnalysis); QVector diagnostics; auto childDiagnostics = clang_getChildDiagnostics(diagnostic); auto numChildDiagnostics = clang_getNumDiagnosticsInSet(childDiagnostics); for (uint j = 0; j < numChildDiagnostics; ++j) { auto childDiagnostic = clang_getDiagnosticInSet(childDiagnostics, j); ClangProblem::Ptr problem(new ClangProblem(childDiagnostic, unit)); diagnostics << ProblemPointer(problem.data()); } setDiagnostics(diagnostics); } IAssistant::Ptr ClangProblem::solutionAssistant() const { if (allFixits().isEmpty()) { return {}; } return IAssistant::Ptr(new ClangFixitAssistant(allFixits())); } ClangFixits ClangProblem::fixits() const { return m_fixits; } void ClangProblem::setFixits(const ClangFixits& fixits) { m_fixits = fixits; } ClangFixits ClangProblem::allFixits() const { ClangFixits result; result << m_fixits; for (const IProblem::Ptr& diagnostic : diagnostics()) { const Ptr problem(dynamic_cast(diagnostic.data())); Q_ASSERT(problem); result << problem->allFixits(); } return result; } ClangFixitAssistant::ClangFixitAssistant(const ClangFixits& fixits) : m_title(tr("Fix-it Hints")) , m_fixits(fixits) { } ClangFixitAssistant::ClangFixitAssistant(const QString& title, const ClangFixits& fixits) : m_title(title) , m_fixits(fixits) { } QString ClangFixitAssistant::title() const { return m_title; } void ClangFixitAssistant::createActions() { KDevelop::IAssistant::createActions(); for (const ClangFixit& fixit : m_fixits) { addAction(IAssistantAction::Ptr(new ClangFixitAction(fixit))); } } ClangFixits ClangFixitAssistant::fixits() const { return m_fixits; } ClangFixitAction::ClangFixitAction(const ClangFixit& fixit) : m_fixit(fixit) { } QString ClangFixitAction::description() const { if (!m_fixit.description.isEmpty()) return m_fixit.description; const auto range = m_fixit.range; if (range.start() == range.end()) { return i18n("Insert \"%1\" at line: %2, column: %3", m_fixit.replacementText, range.start().line()+1, range.start().column()+1); } else if (range.start().line() == range.end().line()) { - return i18n("Replace text at line: %1, column: %2 with: \"%3\"", + if (m_fixit.currentText.isEmpty()) { + return i18n("Replace text at line: %1, column: %2 with: \"%3\"", range.start().line()+1, range.start().column()+1, m_fixit.replacementText); + } else + return i18n("Replace \"%1\" with: \"%2\"", + m_fixit.currentText, m_fixit.replacementText); } else { return i18n("Replace multiple lines starting at line: %1, column: %2 with: \"%3\"", range.start().line()+1, range.start().column()+1, m_fixit.replacementText); } } void ClangFixitAction::execute() { DocumentChangeSet changes; { DUChainReadLocker lock; DocumentChange change(m_fixit.range.document, m_fixit.range, - QString(), m_fixit.replacementText); - // TODO: We probably don't want this - change.m_ignoreOldText = true; + m_fixit.currentText, m_fixit.replacementText); + change.m_ignoreOldText = !m_fixit.currentText.isEmpty(); changes.addChange(change); } changes.setReplacementPolicy(DocumentChangeSet::WarnOnFailedChange); changes.applyAllChanges(); emit executed(this); } diff --git a/languages/clang/duchain/clangproblem.h b/languages/clang/duchain/clangproblem.h index 840ca9e198..790a776b47 100644 --- a/languages/clang/duchain/clangproblem.h +++ b/languages/clang/duchain/clangproblem.h @@ -1,116 +1,118 @@ /* * Copyright 2014 Kevin Funk * * 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 CLANGPROBLEM_H #define CLANGPROBLEM_H #include "clangprivateexport.h" #include #include #include struct KDEVCLANGPRIVATE_EXPORT ClangFixit { QString replacementText; KDevelop::DocumentRange range; QString description; + QString currentText; bool operator==(const ClangFixit& other) const { return replacementText == other.replacementText && range == other.range - && description == other.description; + && description == other.description + && currentText == other.currentText; } }; QDebug KDEVCLANGPRIVATE_EXPORT operator<<(QDebug debug, const ClangFixit& fixit); using ClangFixits = QVector; class KDEVCLANGPRIVATE_EXPORT ClangProblem : public KDevelop::Problem { public: using Ptr = QExplicitlySharedDataPointer; using ConstPtr = QExplicitlySharedDataPointer; /** * Import @p diagnostic into a ClangProblem object * * @param[in] diagnostic To-be-imported clang diagnostic */ ClangProblem(CXDiagnostic diagnostic, CXTranslationUnit unit); KDevelop::IAssistant::Ptr solutionAssistant() const override; ClangFixits fixits() const; void setFixits(const ClangFixits& fixits); /** * Retrieve all fixits of this problem and its child diagnostics * * @return A mapping of problem pointers to the list of associated fixits */ ClangFixits allFixits() const; private: ClangFixits m_fixits; }; class KDEVCLANGPRIVATE_EXPORT ClangFixitAssistant : public KDevelop::IAssistant { Q_OBJECT public: ClangFixitAssistant(const ClangFixits& fixits); ClangFixitAssistant(const QString& title, const ClangFixits& fixits); QString title() const override; void createActions() override; ClangFixits fixits() const; private: QString m_title; ClangFixits m_fixits; }; class KDEVCLANGPRIVATE_EXPORT ClangFixitAction : public KDevelop::IAssistantAction { Q_OBJECT public: ClangFixitAction(const ClangFixit& fixit); QString description() const override; public Q_SLOTS: void execute() override; private: ClangFixit m_fixit; }; #endif // CLANGPROBLEM_H diff --git a/languages/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.cpp b/languages/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.cpp index 6b56e6ba79..bf8784c15c 100644 --- a/languages/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.cpp +++ b/languages/plugins/custom-definesandincludes/compilerprovider/gcclikecompiler.cpp @@ -1,197 +1,193 @@ /* * 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 "gcclikecompiler.h" #include #include #include #include -#include - #include "../debugarea.h" using namespace KDevelop; namespace { // compilers don't deduplicate QStringLiteral QString minusXC() { return QStringLiteral("-xc"); } QString minusXCPlusPlus() { return QStringLiteral("-xc++"); } QStringList languageOptions(const QString& arguments) { // TODO: handle -ansi flag: In C mode, this is equivalent to -std=c90. In C++ mode, it is equivalent to -std=c++98. // TODO: check for -x flag on command line const QRegularExpression regexp("-std=(\\S+)"); // see gcc manpage or llvm/tools/clang/include/clang/Frontend/LangStandards.def for list of valid language options auto result = regexp.match(arguments); if(result.hasMatch()){ auto standard = result.captured(0); QString mode = result.captured(1); QString language; if (mode.startsWith(QLatin1String("c++")) || mode.startsWith(QLatin1String("gnu++"))) { language = minusXCPlusPlus(); } else if (mode.startsWith(QLatin1String("iso9899:"))) { // all iso9899:xxxxx modes are C standards language = minusXC(); } else { // check for c11, gnu99, etc: all of them have a digit after the c/gnu const QRegularExpression cRegexp("(c|gnu)\\d.*"); if (cRegexp.match(mode).hasMatch()) { language = minusXC(); } } if (language.isEmpty()) { qCWarning(DEFINESANDINCLUDES) << "Failed to determine language from -std= flag:" << arguments; language = minusXCPlusPlus(); } return {standard, language}; } // no -std= flag passed -> assume c++11 return {QStringLiteral("-std=c++11"), minusXCPlusPlus()}; } } Defines GccLikeCompiler::defines(const QString& arguments) const { if (!m_definesIncludes.value(arguments).definedMacros.isEmpty() ) { return m_definesIncludes.value(arguments).definedMacros; } // #define a 1 // #define a QRegExp defineExpression( "#define\\s+(\\S+)(?:\\s+(.*)\\s*)?"); QProcess proc; - KDevelop::restoreSystemEnvironment(&proc); proc.setProcessChannelMode( QProcess::MergedChannels ); // TODO: what about -mXXX or -target= flags, some of these change search paths/defines auto compilerArguments = languageOptions(arguments); compilerArguments.append("-dM"); compilerArguments.append("-E"); compilerArguments.append(QProcess::nullDevice()); proc.start(path(), compilerArguments); if ( !proc.waitForStarted( 1000 ) || !proc.waitForFinished( 1000 ) ) { definesAndIncludesDebug() << "Unable to read standard macro definitions from "<< path(); return {}; } while ( proc.canReadLine() ) { auto line = proc.readLine(); if ( defineExpression.indexIn( line ) != -1 ) { m_definesIncludes[arguments].definedMacros[defineExpression.cap( 1 )] = defineExpression.cap( 2 ).trimmed(); } } return m_definesIncludes[arguments].definedMacros; } Path::List GccLikeCompiler::includes(const QString& arguments) const { if ( !m_definesIncludes.value(arguments).includePaths.isEmpty() ) { return m_definesIncludes.value(arguments).includePaths; } QProcess proc; - KDevelop::restoreSystemEnvironment(&proc); proc.setProcessChannelMode( QProcess::MergedChannels ); // The following command will spit out a bunch of information we don't care // about before spitting out the include paths. The parts we care about // look like this: // #include "..." search starts here: // #include <...> search starts here: // /usr/lib/gcc/i486-linux-gnu/4.1.2/../../../../include/c++/4.1.2 // /usr/lib/gcc/i486-linux-gnu/4.1.2/../../../../include/c++/4.1.2/i486-linux-gnu // /usr/lib/gcc/i486-linux-gnu/4.1.2/../../../../include/c++/4.1.2/backward // /usr/local/include // /usr/lib/gcc/i486-linux-gnu/4.1.2/include // /usr/include // End of search list. auto compilerArguments = languageOptions(arguments); compilerArguments.append("-E"); compilerArguments.append("-v"); compilerArguments.append(QProcess::nullDevice()); proc.start(path(), compilerArguments); if ( !proc.waitForStarted( 1000 ) || !proc.waitForFinished( 1000 ) ) { definesAndIncludesDebug() << "Unable to read standard include paths from " << path(); return {}; } // We'll use the following constants to know what we're currently parsing. enum Status { Initial, FirstSearch, Includes, Finished }; Status mode = Initial; foreach( const QString &line, QString::fromLocal8Bit( proc.readAllStandardOutput() ).split( '\n' ) ) { switch ( mode ) { case Initial: if ( line.indexOf( "#include \"...\"" ) != -1 ) { mode = FirstSearch; } break; case FirstSearch: if ( line.indexOf( "#include <...>" ) != -1 ) { mode = Includes; break; } case Includes: //Detect the include-paths by the first space that is prepended. Reason: The list may contain relative paths like "." if ( !line.startsWith( ' ' ) ) { // We've reached the end of the list. mode = Finished; } else { // This is an include path, add it to the list. m_definesIncludes[arguments].includePaths << Path(QFileInfo(line.trimmed()).canonicalFilePath()); } break; default: break; } if ( mode == Finished ) { break; } } return m_definesIncludes[arguments].includePaths; } GccLikeCompiler::GccLikeCompiler(const QString& name, const QString& path, bool editable, const QString& factoryName): ICompiler(name, path, factoryName, editable) {} diff --git a/projectmanagers/cmake/cmakeutils.cpp b/projectmanagers/cmake/cmakeutils.cpp index 4039b2d6fb..5ee58577d4 100644 --- a/projectmanagers/cmake/cmakeutils.cpp +++ b/projectmanagers/cmake/cmakeutils.cpp @@ -1,591 +1,589 @@ /* KDevelop CMake Support * * Copyright 2009 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 "cmakeutils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include -#include #include "icmakedocumentation.h" #include "cmakebuilddirchooser.h" #include "settings/cmakecachemodel.h" #include "debug.h" #include namespace Config { namespace Old { static const QString currentBuildDirKey = "CurrentBuildDir"; static const QString currentCMakeBinaryKey = "Current CMake Binary"; static const QString currentBuildTypeKey = "CurrentBuildType"; static const QString currentInstallDirKey = "CurrentInstallDir"; static const QString currentEnvironmentKey = "CurrentEnvironment"; static const QString currentExtraArgumentsKey = "Extra Arguments"; static const QString projectRootRelativeKey = "ProjectRootRelative"; static const QString projectBuildDirs = "BuildDirs"; } static const QString buildDirIndexKey = "Current Build Directory Index"; static const QString buildDirOverrideIndexKey = "Temporary Build Directory Index"; static const QString buildDirCountKey = "Build Directory Count"; namespace Specific { static const QString buildDirPathKey = "Build Directory Path"; static const QString cmakeBinKey = "CMake Binary"; static const QString cmakeBuildTypeKey = "Build Type"; static const QString cmakeInstallDirKey = "Install Directory"; static const QString cmakeEnvironmentKey = "Environment Profile"; static const QString cmakeArgumentsKey = "Extra Arguments"; } static const QString groupNameBuildDir = "CMake Build Directory %1"; static const QString groupName = "CMake"; } // namespace Config namespace { KConfigGroup baseGroup( KDevelop::IProject* project ) { if (!project) return KConfigGroup(); return project->projectConfiguration()->group( Config::groupName ); } KConfigGroup buildDirGroup( KDevelop::IProject* project, int buildDirIndex ) { return baseGroup(project).group( Config::groupNameBuildDir.arg(buildDirIndex) ); } bool buildDirGroupExists( KDevelop::IProject* project, int buildDirIndex ) { return baseGroup(project).hasGroup( Config::groupNameBuildDir.arg(buildDirIndex) ); } int currentBuildDirIndex( KDevelop::IProject* project ) { KConfigGroup baseGrp = baseGroup(project); if ( baseGrp.hasKey( Config::buildDirOverrideIndexKey ) ) return baseGrp.readEntry( Config::buildDirOverrideIndexKey, 0 ); else return baseGrp.readEntry( Config::buildDirIndexKey, 0 ); // default is 0 because QString::number(0) apparently returns an empty string } QString readProjectParameter( KDevelop::IProject* project, const QString& key, const QString& aDefault ) { int buildDirIndex = currentBuildDirIndex(project); if (buildDirIndex >= 0) return buildDirGroup( project, buildDirIndex ).readEntry( key, aDefault ); else return aDefault; } void writeProjectParameter( KDevelop::IProject* project, const QString& key, const QString& value ) { int buildDirIndex = currentBuildDirIndex(project); if (buildDirIndex >= 0) { KConfigGroup buildDirGrp = buildDirGroup( project, buildDirIndex ); buildDirGrp.writeEntry( key, value ); } else { qWarning() << "cannot write key" << key << "(" << value << ")" << "when no builddir is set!"; } } void writeProjectBaseParameter( KDevelop::IProject* project, const QString& key, const QString& value ) { KConfigGroup baseGrp = baseGroup(project); baseGrp.writeEntry( key, value ); } } // namespace namespace CMake { KDevelop::Path::List resolveSystemDirs(KDevelop::IProject* project, const QStringList& dirs) { const KDevelop::Path buildDir(CMake::currentBuildDir(project)); const KDevelop::Path installDir(CMake::currentInstallDir(project)); KDevelop::Path::List newList; newList.reserve(dirs.size()); foreach(const QString& s, dirs) { KDevelop::Path dir; if(s.startsWith(QString::fromUtf8("#[bin_dir]"))) { dir = KDevelop::Path(buildDir, s); } else if(s.startsWith(QString::fromUtf8("#[install_dir]"))) { dir = KDevelop::Path(installDir, s); } else { dir = KDevelop::Path(s); } // qCDebug(CMAKE) << "resolved" << s << "to" << d; if (!newList.contains(dir)) { newList.append(dir); } } return newList; } ///NOTE: when you change this, update @c defaultConfigure in cmakemanagertest.cpp bool checkForNeedingConfigure( KDevelop::IProject* project ) { const KDevelop::Path builddir = currentBuildDir(project); if( !builddir.isValid() ) { CMakeBuildDirChooser bd; KDevelop::Path folder = project->path(); QString relative=CMake::projectRootRelative(project); folder.cd(relative); bd.setSourceFolder( folder ); bd.setAlreadyUsed( CMake::allBuildDirs(project) ); bd.setCMakeBinary( currentCMakeBinary(project) ); if( !bd.exec() ) { return false; } QString newbuilddir = bd.buildFolder().toLocalFile(); int addedBuildDirIndex = buildDirCount( project ); // old count is the new index // Initialize the kconfig items with the values from the dialog, this ensures the settings // end up in the config file once the changes are saved qCDebug(CMAKE) << "adding to cmake config: new builddir index" << addedBuildDirIndex; qCDebug(CMAKE) << "adding to cmake config: builddir path " << bd.buildFolder(); qCDebug(CMAKE) << "adding to cmake config: installdir " << bd.installPrefix(); qCDebug(CMAKE) << "adding to cmake config: extra args" << bd.extraArguments(); qCDebug(CMAKE) << "adding to cmake config: build type " << bd.buildType(); qCDebug(CMAKE) << "adding to cmake config: cmake binary " << bd.cmakeBinary(); qCDebug(CMAKE) << "adding to cmake config: environment "; CMake::setBuildDirCount( project, addedBuildDirIndex + 1 ); CMake::setCurrentBuildDirIndex( project, addedBuildDirIndex ); CMake::setCurrentBuildDir( project, bd.buildFolder() ); CMake::setCurrentInstallDir( project, bd.installPrefix() ); CMake::setCurrentExtraArguments( project, bd.extraArguments() ); CMake::setCurrentBuildType( project, bd.buildType() ); CMake::setCurrentCMakeBinary( project, bd.cmakeBinary() ); CMake::setCurrentEnvironment( project, QString() ); return true; } else if( !QFile::exists( KDevelop::Path(builddir, "CMakeCache.txt").toLocalFile() ) || //TODO: maybe we could use the builder for that? !(QFile::exists( KDevelop::Path(builddir, "Makefile").toLocalFile() ) || QFile::exists( KDevelop::Path(builddir, "build.ninja").toLocalFile() ) ) ) { // User entered information already, but cmake hasn't actually been run yet. return true; } return false; } QHash enumerateTargets(const KDevelop::Path& targetsFilePath, const QString& sourceDir, const KDevelop::Path &buildDir) { const QString buildPath = buildDir.toLocalFile(); QHash targets; QFile targetsFile(targetsFilePath.toLocalFile()); if (!targetsFile.open(QIODevice::ReadOnly)) { qCDebug(CMAKE) << "Couldn't find the Targets file in" << targetsFile.fileName(); } QTextStream targetsFileStream(&targetsFile); const QRegularExpression rx(QStringLiteral("^(.*)/CMakeFiles/(.*).dir$")); while (!targetsFileStream.atEnd()) { const QString line = targetsFileStream.readLine(); auto match = rx.match(line); if (!match.isValid()) qCDebug(CMAKE) << "invalid match for" << line; const QString sourcePath = match.captured(1).replace(buildPath, sourceDir); targets[KDevelop::Path(sourcePath)].append(match.captured(2)); } return targets; } KDevelop::Path projectRoot(KDevelop::IProject* project) { if (!project) { return {}; } return project->path().cd(CMake::projectRootRelative(project)); } KDevelop::Path currentBuildDir( KDevelop::IProject* project ) { return KDevelop::Path(readProjectParameter( project, Config::Specific::buildDirPathKey, QString() )); } KDevelop::Path commandsFile(KDevelop::IProject* project) { auto currentBuildDir = CMake::currentBuildDir(project); if (currentBuildDir.isEmpty()) { return {}; } return KDevelop::Path(currentBuildDir, QStringLiteral("compile_commands.json")); } KDevelop::Path targetDirectoriesFile(KDevelop::IProject* project) { auto currentBuildDir = CMake::currentBuildDir(project); if (currentBuildDir.isEmpty()) { return {}; } return KDevelop::Path(currentBuildDir, QStringLiteral("CMakeFiles/TargetDirectories.txt")); } QString currentBuildType( KDevelop::IProject* project ) { return readProjectParameter( project, Config::Specific::cmakeBuildTypeKey, "Release" ); } QString findExecutable() { auto cmake = QStandardPaths::findExecutable("cmake"); #ifdef Q_OS_WIN if (cmake.isEmpty()) cmake = QStandardPaths::findExecutable("cmake",{ "C:\\Program Files (x86)\\CMake\\bin", "C:\\Program Files\\CMake\\bin", "C:\\Program Files (x86)\\CMake 2.8\\bin", "C:\\Program Files\\CMake 2.8\\bin"}); #endif return cmake; } KDevelop::Path currentCMakeBinary( KDevelop::IProject* project ) { const auto systemBinary = findExecutable(); auto path = readProjectParameter( project, Config::Specific::cmakeBinKey, systemBinary ); if (path != systemBinary) { QFileInfo info(path); if (!info.isExecutable()) { path = systemBinary; } } return KDevelop::Path(path); } KDevelop::Path currentInstallDir( KDevelop::IProject* project ) { return KDevelop::Path(readProjectParameter( project, Config::Specific::cmakeInstallDirKey, "/usr/local" )); } QString projectRootRelative( KDevelop::IProject* project ) { return baseGroup(project).readEntry( Config::Old::projectRootRelativeKey, "." ); } bool hasProjectRootRelative(KDevelop::IProject* project) { return baseGroup(project).hasKey( Config::Old::projectRootRelativeKey ); } QString currentExtraArguments( KDevelop::IProject* project ) { return readProjectParameter( project, Config::Specific::cmakeArgumentsKey, QString() ); } void setCurrentInstallDir( KDevelop::IProject* project, const KDevelop::Path& path ) { writeProjectParameter( project, Config::Specific::cmakeInstallDirKey, path.toLocalFile() ); } void setCurrentBuildType( KDevelop::IProject* project, const QString& type ) { writeProjectParameter( project, Config::Specific::cmakeBuildTypeKey, type ); } void setCurrentCMakeBinary( KDevelop::IProject* project, const KDevelop::Path& path ) { writeProjectParameter( project, Config::Specific::cmakeBinKey, path.toLocalFile() ); } void setCurrentBuildDir( KDevelop::IProject* project, const KDevelop::Path& path ) { writeProjectParameter( project, Config::Specific::buildDirPathKey, path.toLocalFile() ); } void setProjectRootRelative( KDevelop::IProject* project, const QString& relative) { writeProjectBaseParameter( project, Config::Old::projectRootRelativeKey, relative ); } void setCurrentExtraArguments( KDevelop::IProject* project, const QString& string) { writeProjectParameter( project, Config::Specific::cmakeArgumentsKey, string ); } QString currentEnvironment(KDevelop::IProject* project) { return readProjectParameter( project, Config::Specific::cmakeEnvironmentKey, QString() ); } int currentBuildDirIndex( KDevelop::IProject* project ) { KConfigGroup baseGrp = baseGroup(project); if ( baseGrp.hasKey( Config::buildDirOverrideIndexKey ) ) return baseGrp.readEntry( Config::buildDirOverrideIndexKey, 0 ); else return baseGrp.readEntry( Config::buildDirIndexKey, 0 ); // default is 0 because QString::number(0) apparently returns an empty string } void setCurrentBuildDirIndex( KDevelop::IProject* project, int buildDirIndex ) { writeProjectBaseParameter( project, Config::buildDirIndexKey, QString::number (buildDirIndex) ); } void setCurrentEnvironment( KDevelop::IProject* project, const QString& environment ) { writeProjectParameter( project, Config::Specific::cmakeEnvironmentKey, environment ); } void initBuildDirConfig( KDevelop::IProject* project ) { int buildDirIndex = currentBuildDirIndex( project ); if (buildDirCount(project) <= buildDirIndex ) setBuildDirCount( project, buildDirIndex + 1 ); } int buildDirCount( KDevelop::IProject* project ) { return baseGroup(project).readEntry( Config::buildDirCountKey, 0 ); } void setBuildDirCount( KDevelop::IProject* project, int count ) { writeProjectBaseParameter( project, Config::buildDirCountKey, QString::number(count) ); } void removeBuildDirConfig( KDevelop::IProject* project ) { int buildDirIndex = currentBuildDirIndex( project ); if ( !buildDirGroupExists( project, buildDirIndex ) ) { qWarning() << "build directory config" << buildDirIndex << "to be removed but does not exist"; return; } int bdCount = buildDirCount(project); setBuildDirCount( project, bdCount - 1 ); removeOverrideBuildDirIndex( project ); setCurrentBuildDirIndex( project, -1 ); // move (rename) the upper config groups to keep the numbering // if there's nothing to move, just delete the group physically if (buildDirIndex + 1 == bdCount) buildDirGroup( project, buildDirIndex ).deleteGroup(); else for (int i = buildDirIndex + 1; i < bdCount; ++i) { KConfigGroup src = buildDirGroup( project, i ); KConfigGroup dest = buildDirGroup( project, i - 1 ); dest.deleteGroup(); src.copyTo(&dest); src.deleteGroup(); } } void updateConfig( KDevelop::IProject* project, int buildDirIndex) { if (buildDirIndex < 0) return; KConfigGroup buildDirGrp = buildDirGroup( project, buildDirIndex ); const KDevelop::Path builddir(buildDirGrp.readEntry( Config::Specific::buildDirPathKey, QString() )); const KDevelop::Path cacheFilePath( builddir, QStringLiteral("CMakeCache.txt")); QFile file(cacheFilePath.toLocalFile()); const QMap keys = { { Config::Specific::cmakeBinKey, QStringLiteral("CMAKE_COMMAND") }, { Config::Specific::cmakeInstallDirKey, QStringLiteral("CMAKE_INSTALL_PREFIX") }, { Config::Specific::cmakeBuildTypeKey, QStringLiteral("CMAKE_BUILD_TYPE") } }; if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { QTextStream in(&file); while (!in.atEnd()) { QString line = in.readLine().trimmed(); if(!line.isEmpty() && !line[0].isLetterOrNumber()) { CacheLine c; c.readLine(line); if(c.isCorrect()) { QString key = keys.value(c.name()); if (!key.isEmpty()) { buildDirGrp.writeEntry( key, c.value() ); } } } } } else qCWarning(CMAKE) << "error. Could not find the file" << cacheFilePath; } void attemptMigrate( KDevelop::IProject* project ) { if ( !baseGroup(project).hasKey( Config::Old::projectBuildDirs ) ) { qCDebug(CMAKE) << "CMake settings migration: already done, exiting"; return; } KConfigGroup baseGrp = baseGroup(project); KDevelop::Path buildDir( baseGrp.readEntry( Config::Old::currentBuildDirKey, QString() ) ); int buildDirIndex = -1; const QStringList existingBuildDirs = baseGrp.readEntry( Config::Old::projectBuildDirs, QStringList() ); { // also, find current build directory in this list (we need an index, not path) QString currentBuildDirCanonicalPath = QDir( buildDir.toLocalFile() ).canonicalPath(); for( int i = 0; i < existingBuildDirs.count(); ++i ) { const QString& nextBuildDir = existingBuildDirs.at(i); if( QDir(nextBuildDir).canonicalPath() == currentBuildDirCanonicalPath ) { buildDirIndex = i; } } } int buildDirsCount = existingBuildDirs.count(); qCDebug(CMAKE) << "CMake settings migration: existing build directories" << existingBuildDirs; qCDebug(CMAKE) << "CMake settings migration: build directory count" << buildDirsCount; qCDebug(CMAKE) << "CMake settings migration: current build directory" << buildDir << "(index" << buildDirIndex << ")"; baseGrp.writeEntry( Config::buildDirCountKey, buildDirsCount ); baseGrp.writeEntry( Config::buildDirIndexKey, buildDirIndex ); for (int i = 0; i < buildDirsCount; ++i) { qCDebug(CMAKE) << "CMake settings migration: writing group" << i << ": path" << existingBuildDirs.at(i); KConfigGroup buildDirGrp = buildDirGroup( project, i ); buildDirGrp.writeEntry( Config::Specific::buildDirPathKey, existingBuildDirs.at(i) ); } baseGrp.deleteEntry( Config::Old::currentBuildDirKey ); baseGrp.deleteEntry( Config::Old::currentCMakeBinaryKey ); baseGrp.deleteEntry( Config::Old::currentBuildTypeKey ); baseGrp.deleteEntry( Config::Old::currentInstallDirKey ); baseGrp.deleteEntry( Config::Old::currentEnvironmentKey ); baseGrp.deleteEntry( Config::Old::currentExtraArgumentsKey ); baseGrp.deleteEntry( Config::Old::projectBuildDirs ); } void setOverrideBuildDirIndex( KDevelop::IProject* project, int overrideBuildDirIndex ) { writeProjectBaseParameter( project, Config::buildDirOverrideIndexKey, QString::number(overrideBuildDirIndex) ); } void removeOverrideBuildDirIndex( KDevelop::IProject* project, bool writeToMainIndex ) { KConfigGroup baseGrp = baseGroup(project); if( !baseGrp.hasKey(Config::buildDirOverrideIndexKey) ) return; if( writeToMainIndex ) baseGrp.writeEntry( Config::buildDirIndexKey, baseGrp.readEntry(Config::buildDirOverrideIndexKey) ); baseGrp.deleteEntry(Config::buildDirOverrideIndexKey); } ICMakeDocumentation* cmakeDocumentation() { return KDevelop::ICore::self()->pluginController()->extensionForPlugin("org.kdevelop.ICMakeDocumentation"); } QStringList allBuildDirs(KDevelop::IProject* project) { QStringList result; int bdCount = buildDirCount(project); for (int i = 0; i < bdCount; ++i) result += buildDirGroup( project, i ).readEntry( Config::Specific::buildDirPathKey ); return result; } QString executeProcess(const QString& execName, const QStringList& args) { Q_ASSERT(!execName.isEmpty()); qCDebug(CMAKE) << "Executing:" << execName << "::" << args /*<< "into" << *m_vars*/; QProcess p; - KDevelop::restoreSystemEnvironment(&p); QTemporaryDir tmp("kdevcmakemanager"); p.setWorkingDirectory( tmp.path() ); p.start(execName, args, QIODevice::ReadOnly); if(!p.waitForFinished()) { qCDebug(CMAKE) << "failed to execute:" << execName; } QByteArray b = p.readAllStandardOutput(); QString t; t.prepend(b.trimmed()); return t; } } diff --git a/projectmanagers/cmake/parser/cmakeprojectvisitor.cpp b/projectmanagers/cmake/parser/cmakeprojectvisitor.cpp index 2782390511..4eedf02b56 100644 --- a/projectmanagers/cmake/parser/cmakeprojectvisitor.cpp +++ b/projectmanagers/cmake/parser/cmakeprojectvisitor.cpp @@ -1,2603 +1,2599 @@ /* KDevelop CMake Support * * Copyright 2007-2008 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 "cmakeprojectvisitor.h" #include "cmakeast.h" #include "cmakecondition.h" #include "astfactory.h" #include "cmakeduchaintypes.h" #include "cmakeparserutils.h" #include "../debug.h" #include #include #include #include #include #include #include #include #include -#include - #include #include #include #include #include #include #include #include #include #include using namespace KDevelop; static void debugMsgs(const QString& message) { qCDebug(CMAKE) << "message:" << message; } static bool isGenerated(const QString& name) { return name.indexOf("#[")>=0; } CMakeProjectVisitor::message_callback CMakeProjectVisitor::s_msgcallback=debugMsgs; CMakeProjectVisitor::CMakeProjectVisitor(const QString& root, ReferencedTopDUContext parent) : m_root(root), m_vars(0), m_macros(0), m_cache(0) , m_topctx(0), m_parentCtx(parent), m_hitBreak(false), m_hitReturn(false) { } QStringList CMakeProjectVisitor::envVarDirectories(const QString &varName) const { QString env; QMap::const_iterator it=m_environmentProfile.constFind(varName); if(it!=m_environmentProfile.constEnd()) env = *it; else env = QString::fromLatin1(qgetenv(varName.toLatin1())); // qCDebug(CMAKE) << ".......resolving env:" << varName << "=" << QProcess::systemEnvironment() << env; if(!env.isEmpty()) { QChar separator; #ifdef Q_OS_WIN separator = ';'; #else separator = ':'; #endif qCDebug(CMAKE) << "resolving env:" << varName << "=" << env; return env.split(separator); } else { qCDebug(CMAKE) << "warning:" << varName << " not found"; return QStringList(); } } QList< CMakeProjectVisitor::IntPair > CMakeProjectVisitor::parseArgument(const QString &exp) { QString name; Stack opened; QList< IntPair > pos; bool gotDollar=false; for(int i=exp.indexOf('$'); i=0; i++) { switch(exp[i].unicode()) { case '$': gotDollar=true; break; case '{': if(gotDollar) { opened.push(i); } gotDollar=false; break; case '}': if(!opened.isEmpty()) { // note: don't merge this into the function call below, // the evaluation order is undefined then! int start = opened.pop(); pos.append(IntPair(start, i, opened.count() + 1)); } break; } } for(int i=pos.count()-1; i>=0 && !opened.isEmpty(); i--) { if(pos[i].first==opened.top()) opened.pop(); pos[i].level -= opened.size(); } return pos; } QStringList CMakeProjectVisitor::variableValue(const QString& var) const { VariableMap::const_iterator it=m_vars->constFind(var); if(it!=m_vars->constEnd()) return *it; else { CacheValues::const_iterator it=m_cache->constFind(var); if(it!=m_cache->constEnd()) return it->value.split(';'); } return QStringList(); } QStringList CMakeProjectVisitor::theValue(const QString& exp, const IntPair& thecase) const { int dollar=exp.lastIndexOf('$', thecase.first); QString type=exp.mid(dollar+1, thecase.first-dollar-1); QString var=exp.mid(thecase.first+1, thecase.second-thecase.first-1); QStringList value; // qCDebug(CMAKE) << "lalalallalala" << exp << thecase.print(); if(type.isEmpty()) { value=variableValue(var); } else if(type=="ENV") { value=envVarDirectories(var); } else qCDebug(CMAKE) << "error: I do not understand the key: " << type; // qCDebug(CMAKE) << "solving: " << var << vars << exp; return value; } static QString replaceOne(const QString& var, const QString& id, const QString& value, int dollar) { // qCDebug(CMAKE) << "ooo" << var << value << id << var[dollar+id.size()-1] << (dollar+id.size()); // qCDebug(CMAKE) << "kkkk" << var.mid(0, dollar) << value << var.mid(dollar+id.size(), var.size()-(dollar+id.size())); return var.mid(0, dollar)+value+var.mid(dollar+id.size(), var.size()-(dollar+id.size())); } QStringList CMakeProjectVisitor::value(const QString& exp, const QList& poss, int& desired) const { QString var=exp; QList invars; invars += poss[desired]; //qCDebug(CMAKE) << ">>>>>" << exp << desired << poss.count(); for(; desired+11; desired++) { invars+=poss[desired+1]; //qCDebug(CMAKE) << "poss@"<< desired+1 << "="<< poss[desired+1].print(); } //qCDebug(CMAKE) << ";;;;;" << invars.count(); if(invars.count()>1) { QList::const_iterator itConstEnd=invars.constEnd(); QList::iterator itEnd=invars.end(); QList::iterator itBegin=invars.begin(); for(QList::const_iterator it=invars.constBegin(); (it+1)!=itConstEnd; ++it) { const IntPair& subvar=*it; int dollar=var.lastIndexOf('$', subvar.first); QString id=var.mid(dollar, subvar.second-dollar+1), value=theValue(var, subvar).join(QChar(';')); int diff=value.size()-id.size(); for(QList::iterator it=itBegin; it!=itEnd; ++it) { if(it->first > subvar.first) it->first += diff; if(it->second> subvar.second) it->second+= diff; } var=replaceOne(var, id, value, dollar); } } return theValue(var, invars.last()); } QStringList CMakeProjectVisitor::resolveVariable(const CMakeFunctionArgument &exp) { QStringList ret; ret += QString(); QList< IntPair > var = parseArgument(exp.value); int i=0; IntPair last(-1,-1, 0); for(QList::const_iterator it=var.constBegin(); it!=var.constEnd(); ++it, ++i) { while(it!=var.constEnd() && it->level>1) ++it; const IntPair& p=*it; // qCDebug(CMAKE) << "reeeeeet" << ret << exp.value << p.print(); int dollar=exp.value.lastIndexOf('$', p.first); QString pre=exp.value.mid(last.second+1, dollar-last.second-1); QStringList vars = value(exp.value, var, i); // qCDebug(CMAKE) << "aaaaaaaaaA" << pre << vars; if(!vars.isEmpty()) { pre+=vars.takeFirst(); } ret.last()+=pre; ret += vars; last=p; // qCDebug(CMAKE) << "yaaaaaaa" << ret; // i++; } ret.last().append(exp.value.mid(last.second+1, exp.value.count()-last.second)); if(exp.quoted) { ret=QStringList(ret.join(QChar(';'))); } else if(ret.size()==1 && ret.first().isEmpty()) { ret.clear(); } return ret; } bool CMakeProjectVisitor::hasMacro(const QString& name) const { Q_ASSERT(m_macros); return m_macros->contains(name); } int CMakeProjectVisitor::visit(const CMakeAst *ast) { qCDebug(CMAKE) << "error! function not implemented" << ast->content()[ast->line()].name; foreach(const CMakeFunctionArgument& arg, ast->outputArguments()) { //NOTE: this is a workaround, but fixes some issues. qCDebug(CMAKE) << "reseting: " << arg.value; m_vars->insert(arg.value, QStringList()); } return 1; } int CMakeProjectVisitor::visit( const AddTestAst * test) { Test t; t.name = test->testName(); t.executable = test->exeName(); t.arguments = test->testArgs(); // Strip the extensions and full path added by kde4_add_unit_test, //this way it's much more useful, e.g. we can pass it to gdb if (t.executable.endsWith(".shell")) { t.executable.chop(6); } else if (t.executable.endsWith(".bat")) { t.executable.chop(4); } qCDebug(CMAKE) << "AddTestAst" << t.executable; m_testSuites << t; return 1; } int CMakeProjectVisitor::visit(const ProjectAst *project) { m_projectName = project->projectName(); m_vars->insertGlobal("CMAKE_PROJECT_NAME", QStringList(project->projectName())); m_vars->insert("PROJECT_NAME", QStringList(project->projectName())); m_vars->insertGlobal("PROJECT_SOURCE_DIR", m_vars->value("CMAKE_CURRENT_SOURCE_DIR")); m_vars->insertGlobal("PROJECT_BINARY_DIR", m_vars->value("CMAKE_CURRENT_BINARY_DIR")); m_vars->insertGlobal(QString("%1_SOURCE_DIR").arg(m_projectName), m_vars->value("CMAKE_CURRENT_SOURCE_DIR")); m_vars->insertGlobal(QString("%1_BINARY_DIR").arg(m_projectName), m_vars->value("CMAKE_CURRENT_BINARY_DIR")); return 1; } int CMakeProjectVisitor::visit( const SetTargetPropsAst * targetProps) { qCDebug(CMAKE) << "setting target props for " << targetProps->targets() << targetProps->properties(); foreach(const QString& _tname, targetProps->targets()) { QString tname = m_targetAlias.value(_tname, _tname); foreach(const SetTargetPropsAst::PropPair& t, targetProps->properties()) { m_props[TargetProperty][tname][t.first] = t.second.split(';'); } } return 1; } int CMakeProjectVisitor::visit( const SetDirectoryPropsAst * dirProps) { QString dir=m_vars->value("CMAKE_CURRENT_SOURCE_DIR").join(QString()); qCDebug(CMAKE) << "setting directory props for " << dirProps->properties() << dir; QMap& dprops = m_props[DirectoryProperty][dir]; foreach(const SetDirectoryPropsAst::PropPair& t, dirProps->properties()) { dprops[t.first] = t.second.split(';'); } return 1; } int CMakeProjectVisitor::visit( const GetTargetPropAst * prop) { QString targetName = prop->target(); qCDebug(CMAKE) << "getting target " << targetName << " prop " << prop->property() << prop->variableName(); QStringList value; CategoryType& category = m_props[TargetProperty]; CategoryType::iterator itTarget = category.find(m_targetAlias.value(targetName, targetName)); if(itTarget!=category.end()) { QMap& targetProps = itTarget.value(); if(!targetProps.contains(prop->property())) { if(prop->property().startsWith("LOCATION_") && targetProps.contains("IMPORTED_"+prop->property())) targetProps[prop->property()] = targetProps["IMPORTED_"+prop->property()]; } value = targetProps.value(prop->property()); } if(value.isEmpty()) value += QString(prop->variableName()+"-NOTFOUND"); m_vars->insert(prop->variableName(), value); // qCDebug(CMAKE) << "goooooot" << m_vars->value(prop->variableName()); return 1; } int CMakeProjectVisitor::visit(const AddSubdirectoryAst *subd) { qCDebug(CMAKE) << "adding subdirectory" << subd->sourceDir(); VisitorState p=stackTop(); Subdirectory d; d.name=subd->sourceDir(); d.build_dir=subd->binaryDir().isEmpty() ? d.name : subd->binaryDir(); d.desc=p.code->at(p.line); m_subdirectories += d; return 1; } int CMakeProjectVisitor::visit(const SubdirsAst *sdirs) { qCDebug(CMAKE) << "adding subdirectories" << sdirs->directories() << sdirs->exluceFromAll(); VisitorState p=stackTop(); CMakeFunctionDesc desc=p.code->at(p.line); foreach(const QString& dir, sdirs->directories() + sdirs->exluceFromAll()) { Subdirectory d; d.name=dir; d.build_dir=dir; d.desc=desc; m_subdirectories += d; } return 1; } void CMakeProjectVisitor::printBacktrace(const Stack &backtrace) { int i=0; qCDebug(CMAKE) << "backtrace" << backtrace.count(); foreach(const VisitorState& v, backtrace) { if(v.code->count()>v.line) qCDebug(CMAKE) << i << ": ";// << v.code->at(v.line).name; else qCDebug(CMAKE) << i << ": ------------------------"; i++; } } CMakeProjectVisitor::VisitorState CMakeProjectVisitor::stackTop() const { VisitorState p = {}; QString filename=m_backtrace.front().code->at(m_backtrace.front().line).filePath; Stack::const_iterator it=m_backtrace.constBegin(); for(; it!=m_backtrace.constEnd(); ++it) { if(filename!=it->code->at(it->line).filePath) break; p=*it; } return p; } void CMakeProjectVisitor::defineTarget(const QString& _id, const QStringList& sources, Target::Type t) { QString id = _id.isEmpty() ? "" : _id; qCDebug(CMAKE) << "Defining target" << id; if (m_targetForId.contains(id)) qCDebug(CMAKE) << "warning! there already was a target called" << id; VisitorState p=stackTop(); Declaration *d=0; if(!p.code->at(p.line).arguments.isEmpty()) { DUChainWriteLocker lock(DUChain::lock()); d= new Declaration(p.code->at(p.line).arguments.first().range(), p.context); d->setIdentifier( Identifier(id) ); AbstractType::Ptr targetType(new TargetType); d->setAbstractType(targetType); } QMap& targetProps = m_props[TargetProperty][id]; QString exe=id, locationDir; switch(t) { case Target::Executable: { exe += m_vars->value("CMAKE_EXECUTABLE_SUFFIX").join(QString()); locationDir = m_vars->value("CMAKE_RUNTIME_OUTPUT_DIRECTORY").join(QString()); targetProps["RUNTIME_OUTPUT_DIRECTORY"] = QStringList(locationDir); } break; case Target::Library: { exe = QString("%1%2%3").arg(m_vars->value("CMAKE_LIBRARY_PREFIX").join(QString())) .arg(id) .arg(m_vars->value("CMAKE_LIBRARY_SUFFIX").join(QString())); locationDir = m_vars->value("CMAKE_LIBRARY_OUTPUT_DIRECTORY").join(QString()); targetProps["LIBRARY_OUTPUT_DIRECTORY"] = QStringList(locationDir); } break; case Target::Custom: break; } if(locationDir.isEmpty()) { locationDir = m_vars->value("CMAKE_CURRENT_BINARY_DIR").join(QString()); } Target target; target.name=id; target.declaration=IndexedDeclaration(d); target.files=sources; target.type=t; target.desc=p.code->at(p.line); m_targetForId[target.name]=target; if(CMakeCondition::textIsTrue(m_vars->value("CMAKE_INCLUDE_CURRENT_DIR_IN_INTERFACE").join(QString()))) targetProps["INTERFACE_INCLUDE_DIRECTORIES"] = (m_vars->value("CMAKE_CURRENT_BINARY_DIR") + m_vars->value("CMAKE_CURRENT_SOURCE_DIR")); targetProps["OUTPUT_NAME"] = QStringList(exe); targetProps["LOCATION"] = QStringList(locationDir+'/'+exe); } int CMakeProjectVisitor::visit(const AddExecutableAst *exec) { if(!exec->isImported()) defineTarget(exec->executable(), exec->sourceLists(), Target::Executable); else qCDebug(CMAKE) << "imported executable" << exec->executable(); qCDebug(CMAKE) << "exec:" << exec->executable() << "->" << m_targetForId.contains(exec->executable()) << "imported" << exec->isImported(); return 1; } int CMakeProjectVisitor::visit(const AddLibraryAst *lib) { if(lib->isAlias()) m_targetAlias[lib->libraryName()] = lib->aliasTarget(); else if(!lib->isImported()) defineTarget(lib->libraryName(), lib->sourceLists(), Target::Library); qCDebug(CMAKE) << "lib:" << lib->libraryName(); return 1; } int CMakeProjectVisitor::visit(const SetAst *set) { //TODO: Must deal with ENV{something} case if(set->storeInCache()) { QStringList values; CacheValues::const_iterator itCache= m_cache->constFind(set->variableName()); if(itCache!=m_cache->constEnd()) values = itCache->value.split(';'); else values = set->values(); m_vars->insertGlobal(set->variableName(), values); } else m_vars->insert(set->variableName(), set->values(), set->parentScope()); // qCDebug(CMAKE) << "setting variable:" << set->variableName() << set->parentScope() // << "to" << m_vars->value(set->variableName()) << set->storeInCache() // ; return 1; } int CMakeProjectVisitor::visit(const UnsetAst* unset) { if(unset->env()) { qCDebug(CMAKE) << "error! can't unset the env var: " << unset->variableName(); } else { m_vars->remove(unset->variableName()); if(unset->cache()) { qCDebug(CMAKE) << "error! can't unset the cached var: " << unset->variableName(); } } qCDebug(CMAKE) << "unset variable:" << unset->variableName(); return 1; } int CMakeProjectVisitor::visit(const IncludeDirectoriesAst * dirs) { qCDebug(CMAKE) << "adding include directories" << dirs->includedDirectories(); IncludeDirectoriesAst::IncludeType t = dirs->includeType(); QStringList toInclude = dirs->includedDirectories(); if(t==IncludeDirectoriesAst::Default) { if(m_vars->value("CMAKE_INCLUDE_DIRECTORIES_BEFORE")==QStringList("ON")) t = IncludeDirectoriesAst::Before; else t = IncludeDirectoriesAst::After; } QString dir = m_vars->value("CMAKE_CURRENT_SOURCE_DIR").join(QString()); QStringList& v = m_props[DirectoryProperty][dir]["INCLUDE_DIRECTORIES"]; if(t==IncludeDirectoriesAst::After) v += toInclude; else { v = toInclude + v; } qCDebug(CMAKE) << "done." << v; return 1; } QString CMakeProjectVisitor::findFile(const QString &file, const QStringList &folders, const QStringList& suffixes, bool location) { if( file.isEmpty() || QFileInfo(file).isAbsolute() ) return file; QStringList suffixFolders, useSuffixes(suffixes); useSuffixes.prepend(QString()); foreach(const QString& apath, folders) { foreach(const QString& suffix, useSuffixes) { suffixFolders.append(apath+'/'+suffix); } } suffixFolders.removeDuplicates(); QUrl path; foreach(const QString& mpath, suffixFolders) { if(mpath.isEmpty()) continue; QUrl afile(mpath); afile = afile.adjusted(QUrl::StripTrailingSlash); afile.setPath(afile.path() + '/' + file); qCDebug(CMAKE) << "Trying:" << mpath << '.' << file; QFileInfo f(afile.toLocalFile()); if(f.exists() && f.isFile()) { if(location) path=mpath; else path=afile; break; } } //qCDebug(CMAKE) << "find file" << file << "into:" << folders << "found at:" << path; return path.adjusted(QUrl::StripTrailingSlash).toLocalFile(); } int CMakeProjectVisitor::visit(const IncludeAst *inc) { Q_ASSERT(m_vars->contains("CMAKE_CURRENT_SOURCE_DIR")); const QStringList modulePath = m_vars->value("CMAKE_MODULE_PATH") + m_modulePath + m_vars->value("CMAKE_CURRENT_SOURCE_DIR"); qCDebug(CMAKE) << "Include:" << inc->includeFile() << "@" << modulePath << " into "; QString possib=inc->includeFile(); QString path; if(!QUrl(possib).isRelative() && QFile::exists(possib)) path=possib; else { if(!possib.contains('.')) possib += ".cmake"; path=findFile(possib, modulePath); } if(!path.isEmpty()) { m_vars->insertMulti("CMAKE_CURRENT_LIST_FILE", QStringList(path)); m_vars->insertMulti("CMAKE_CURRENT_LIST_DIR", QStringList(QUrl(path).directory())); CMakeFileContent include = CMakeListsParser::readCMakeFile(path); if ( !include.isEmpty() ) { qCDebug(CMAKE) << "including:" << path; walk(include, 0, true); m_hitReturn = false; } else { //FIXME: Put here the error. qCDebug(CMAKE) << "Include. Parsing error."; } Q_ASSERT(m_vars->value("CMAKE_CURRENT_LIST_FILE")==QStringList(path)); m_vars->removeMulti("CMAKE_CURRENT_LIST_FILE"); m_vars->removeMulti("CMAKE_CURRENT_LIST_DIR"); } else { if(!inc->optional()) { qCDebug(CMAKE) << "error!! Could not find" << inc->includeFile() << "=" << possib << "into" << modulePath; } } if(!inc->resultVariable().isEmpty()) { QString result="NOTFOUND"; if(!path.isEmpty()) result=path; m_vars->insert(inc->resultVariable(), QStringList(result)); } qCDebug(CMAKE) << "include of" << inc->includeFile() << "done."; return 1; } int CMakeProjectVisitor::visit(const FindPackageAst *pack) { m_vars->remove(pack->name()+"-NOTFOUND"); qCDebug(CMAKE) << "Find:" << pack->name() << "package." << pack->version() << m_modulePath << "No module: " << pack->noModule(); QStringList possibleModuleNames; if(!pack->noModule()) //TODO Also implied by a whole slew of additional options. { // Look for a Find{package}.cmake QString possib=pack->name(); if(!possib.endsWith(".cmake")) possib += ".cmake"; possib.prepend("Find"); possibleModuleNames += possib; } const QStringList modulePath = m_vars->value("CMAKE_MODULE_PATH") + m_modulePath + pack->paths(); QString name=pack->name(); QStringList postfix=QStringList() << QString() << "/cmake" << "/CMake"; QStringList configPath; QStringList lookupPaths = envVarDirectories("CMAKE_PREFIX_PATH") + m_vars->value("CMAKE_PREFIX_PATH") + m_vars->value("CMAKE_SYSTEM_PREFIX_PATH"); // note: should note be done if NO_SYSTEM_ENVIRONMENT_PATH is set, see docs: /* 4. Search the standard system environment variables. This can be skipped * if NO_SYSTEM_ENVIRONMENT_PATH is passed. Path entries ending in "/bin" or * "/sbin" are automatically converted to their parent directories. */ foreach(const QString& lookup, envVarDirectories("PATH")) { if (lookup.endsWith("/bin")) { lookupPaths << lookup.left(lookup.length() - 4); } else if (lookup.endsWith("/sbin")) { lookupPaths << lookup.left(lookup.length() - 5); } else { lookupPaths << lookup; } } const bool useLib64 = m_props[GlobalProperty][QString()]["FIND_LIBRARY_USE_LIB64_PATHS"].contains("TRUE"); QSet handled; foreach(const QString& lookup, lookupPaths) { if(!QFile::exists(lookup) || handled.contains(lookup)) { continue; } foreach(const QString& post, postfix) { configPath.prepend(lookup+"/share/"+name.toLower()+post); configPath.prepend(lookup+"/share/"+name+post); configPath.prepend(lookup+"/share/cmake/"+name.toLower()+post); configPath.prepend(lookup+"/share/cmake/"+name+post); configPath.prepend(lookup+"/lib/"+name.toLower()+post); configPath.prepend(lookup+"/lib/"+name+post); configPath.prepend(lookup+"/lib/cmake/"+name.toLower()+post); configPath.prepend(lookup+"/lib/cmake/"+name+post); if (useLib64) { configPath.prepend(lookup+"/lib64/"+name.toLower()+post); configPath.prepend(lookup+"/lib64/"+name+post); configPath.prepend(lookup+"/lib64/cmake/"+name.toLower()+post); configPath.prepend(lookup+"/lib64/cmake/"+name+post); } } handled << lookup; } QString varName=pack->name()+"_DIR"; if(m_cache->contains(varName)) configPath.prepend(m_cache->value(varName).value); QStringList possibleConfigNames; possibleConfigNames+=QString("%1Config.cmake").arg(pack->name()); possibleConfigNames+=QString("%1-config.cmake").arg(pack->name().toLower()); bool isConfig=false; QString path; foreach(const QString& possib, possibleConfigNames) { path = findFile(possib, configPath); if (!path.isEmpty()) { m_vars->insertGlobal(pack->name()+"_DIR", QStringList(QUrl(path).directory())); isConfig=true; break; } } if (path.isEmpty()) { foreach(const QString& possib, possibleModuleNames) { path=findFile(possib, modulePath); if(!path.isEmpty()) { break; } } } if(!path.isEmpty()) { m_vars->insertMulti("CMAKE_CURRENT_LIST_FILE", QStringList(path)); m_vars->insertMulti("CMAKE_CURRENT_LIST_DIR", QStringList(QUrl(path).directory())); if(pack->isRequired()) m_vars->insert(pack->name()+"_FIND_REQUIRED", QStringList("TRUE")); if(pack->isQuiet()) m_vars->insert(pack->name()+"_FIND_QUIET", QStringList("TRUE")); if(!pack->components().isEmpty()) m_vars->insert(pack->name()+"_FIND_COMPONENTS", pack->components()); m_vars->insert(pack->name()+"_FIND_VERSION", QStringList(pack->version())); QStringList version = pack->version().split('.'); if(version.size()>=1) m_vars->insert(pack->name()+"_FIND_VERSION_MAJOR", QStringList(version[0])); if(version.size()>=2) m_vars->insert(pack->name()+"_FIND_VERSION_MINOR", QStringList(version[1])); if(version.size()>=3) m_vars->insert(pack->name()+"_FIND_VERSION_PATCH", QStringList(version[2])); if(version.size()>=4) m_vars->insert(pack->name()+"_FIND_VERSION_TWEAK", QStringList(version[3])); m_vars->insert(pack->name()+"_FIND_VERSION_COUNT", QStringList(QString::number(version.size()))); CMakeFileContent package=CMakeListsParser::readCMakeFile( path ); if ( !package.isEmpty() ) { path=QUrl(path).pathOrUrl(); qCDebug(CMAKE) << "================== Found" << path << "==============="; walk(package, 0, true); m_hitReturn = false; } else { qCDebug(CMAKE) << "error: find_package. Parsing error." << path; } if(pack->noModule()) { m_vars->insertGlobal(QString("%1_CONFIG").arg(pack->name()), QStringList(path)); } m_vars->removeMulti("CMAKE_CURRENT_LIST_FILE"); m_vars->removeMulti("CMAKE_CURRENT_LIST_DIR"); if(isConfig) { m_vars->insert(pack->name()+"_FOUND", QStringList("TRUE")); m_vars->insert(pack->name().toUpper()+"_FOUND", QStringList("TRUE")); } } else { if(pack->isRequired()) { //FIXME: Put here the error. qCDebug(CMAKE) << "error: Could not find" << pack->name() << "into" << modulePath; } m_vars->insertGlobal(QString("%1_DIR").arg(pack->name()), QStringList(QString("%1_DIR-NOTFOUND").arg(pack->name()))); } qCDebug(CMAKE) << "Exit. Found:" << pack->name() << m_vars->value(pack->name()+"_FOUND"); return 1; } KDevelop::ReferencedTopDUContext CMakeProjectVisitor::createContext(const IndexedString& idxpath, ReferencedTopDUContext aux, int endl ,int endc, bool isClean) { DUChainWriteLocker lock(DUChain::lock()); KDevelop::ReferencedTopDUContext topctx=DUChain::self()->chainForDocument(idxpath); if(topctx) { if(isClean) { topctx->deleteLocalDeclarations(); topctx->deleteChildContextsRecursively(); topctx->deleteUses(); } foreach(DUContext* importer, topctx->importers()) importer->removeImportedParentContext(topctx); topctx->clearImportedParentContexts(); } else { ParsingEnvironmentFile* env = new ParsingEnvironmentFile(idxpath); env->setLanguage(IndexedString("cmake")); topctx=new TopDUContext(idxpath, RangeInRevision(0,0, endl, endc), env); DUChain::self()->addDocumentChain(topctx); Q_ASSERT(DUChain::self()->chainForDocument(idxpath)); } //Clean the re-used top-context. This is problematic since it may affect independent projects, but it's better then letting things accumulate. ///@todo This is problematic when the same file is used from within multiple CMakeLists.txts, /// for example a standard import like FindKDE4.cmake, because it creates a cross-dependency /// between the topducontext's of independent projects, like for example kdebase and kdevplatform ///@todo Solve that by creating unique versions of all used top-context on a per-project basis using ParsingEnvironmentFile for disambiguation. topctx->addImportedParentContext(aux); /// @todo should we check for NULL or assert? if (aux) aux->addImportedParentContext(topctx); return topctx; } bool CMakeProjectVisitor::haveToFind(const QString &varName) { if(m_vars->contains(varName+"_FOUND")) return false; m_vars->remove(varName+"-NOTFOUND"); return true; } int CMakeProjectVisitor::visit(const FindProgramAst *fprog) { if(!haveToFind(fprog->variableName())) return 1; if(m_cache->contains(fprog->variableName())) { qCDebug(CMAKE) << "FindProgram: cache" << fprog->variableName() << m_cache->value(fprog->variableName()).value; return 1; } QStringList modulePath = fprog->path(); #ifdef Q_OS_WIN if(!fprog->noSystemEnvironmentPath() && !fprog->noDefaultPath()) modulePath += envVarDirectories("Path"); qCDebug(CMAKE) << "added Path env for program finding" << envVarDirectories("Path"); #else if(!fprog->noSystemEnvironmentPath() && !fprog->noDefaultPath()) modulePath += envVarDirectories("PATH"); #endif qCDebug(CMAKE) << "Find:" << fprog->variableName() << fprog->filenames() << "program into" << modulePath<<":"<< fprog->path(); QString path; foreach(const QString& filename, fprog->filenames()) { path=findExecutable(filename, modulePath, fprog->pathSuffixes()); if(!path.isEmpty()) break; } if(!path.isEmpty()) m_vars->insertGlobal(fprog->variableName(), QStringList(path)); else m_vars->insertGlobal(fprog->variableName()+"-NOTFOUND", QStringList()); qCDebug(CMAKE) << "FindProgram:" << fprog->variableName() << "=" << m_vars->value(fprog->variableName()) << modulePath; return 1; } QString CMakeProjectVisitor::findExecutable(const QString& file, const QStringList& directories, const QStringList& pathSuffixes) const { QString path; QStringList suffixes=m_vars->value("CMAKE_EXECUTABLE_SUFFIX"); suffixes.prepend(QString()); qCDebug(CMAKE) << "finding executable, using suffixes" << suffixes; foreach(const QString& suffix, suffixes) { path=findFile(file+suffix, directories, pathSuffixes); if(!path.isEmpty()) break; } return path; } int CMakeProjectVisitor::visit(const FindPathAst *fpath) { if(!haveToFind(fpath->variableName())) return 1; if(m_cache->contains(fpath->variableName())) { qCDebug(CMAKE) << "FindPath: cache" << fpath->variableName(); return 1; } QStringList locationOptions = fpath->path()+fpath->hints(); QStringList path, files=fpath->filenames(); QStringList suffixes=fpath->pathSuffixes(); if(!fpath->noDefaultPath()) { QStringList pp = envVarDirectories("CMAKE_PREFIX_PATH") + m_vars->value("CMAKE_PREFIX_PATH"); foreach(const QString& path, pp) { locationOptions += path+"/include"; } locationOptions += pp; locationOptions += envVarDirectories("CMAKE_INCLUDE_PATH") + m_vars->value("CMAKE_INCLUDE_PATH"); locationOptions += m_vars->value("CMAKE_FRAMEWORK_PATH"); pp=m_vars->value("CMAKE_SYSTEM_PREFIX_PATH"); foreach(const QString& path, pp) { locationOptions += path+"/include"; } locationOptions += m_vars->value("CMAKE_SYSTEM_INCLUDE_PATH"); locationOptions += m_vars->value("CMAKE_SYSTEM_FRAMEWORK_PATH"); } qCDebug(CMAKE) << "Find:" << /*locationOptions << "@" <<*/ fpath->variableName() << /*"=" << files <<*/ " path."; foreach(const QString& p, files) { QString p1=findFile(p, locationOptions, suffixes, true); if(p1.isEmpty()) { qCDebug(CMAKE) << p << "not found"; } else { path += p1; } } if(!path.isEmpty()) { m_vars->insertGlobal(fpath->variableName(), QStringList(path)); } else { qCDebug(CMAKE) << "Path not found"; } qCDebug(CMAKE) << "Find path: " << fpath->variableName() << m_vars->value(fpath->variableName()); // m_vars->insert(fpath->variableName()+"-NOTFOUND", QStringList()); return 1; } int CMakeProjectVisitor::visit(const FindLibraryAst *flib) { if(!haveToFind(flib->variableName())) return 1; if(m_cache->contains(flib->variableName())) { qCDebug(CMAKE) << "FindLibrary: cache" << flib->variableName(); return 1; } QStringList locationOptions = flib->path()+flib->hints(); QStringList files=flib->filenames(); QString path; if(!flib->noDefaultPath()) { QStringList opt = envVarDirectories("CMAKE_PREFIX_PATH") + m_vars->value("CMAKE_PREFIX_PATH"); foreach(const QString& s, opt) locationOptions.append(s+"/lib"); locationOptions += envVarDirectories("CMAKE_LIBRARY_PATH") + m_vars->value("CMAKE_LIBRARY_PATH"); locationOptions += m_vars->value("CMAKE_FRAMEWORK_PATH"); locationOptions += m_vars->value("CMAKE_SYSTEM_LIBRARY_PATH"); locationOptions += m_vars->value("CMAKE_PLATFORM_IMPLICIT_LINK_DIRECTORIES"); opt=m_vars->value("CMAKE_SYSTEM_PREFIX_PATH"); foreach(const QString& s, opt) locationOptions.append(s+"/lib"); } foreach(const QString& p, files) { foreach(const QString& prefix, m_vars->value("CMAKE_FIND_LIBRARY_PREFIXES")) { foreach(const QString& suffix, m_vars->value("CMAKE_FIND_LIBRARY_SUFFIXES")) { QString p1=findFile(prefix+p+suffix, locationOptions, flib->pathSuffixes()); if(p1.isEmpty()) { qCDebug(CMAKE) << p << "not found"; } else { path = p1; break; } } if(!path.isEmpty()) break; } if(!path.isEmpty()) break; } if(!path.isEmpty()) { m_vars->insertGlobal(flib->variableName(), QStringList(path)); } else qCDebug(CMAKE) << "error. Library" << flib->filenames() << "not found"; // m_vars->insert(fpath->variableName()+"-NOTFOUND", QStringList()); qCDebug(CMAKE) << "Find Library:" << flib->filenames() << m_vars->value(flib->variableName()); return 1; } int CMakeProjectVisitor::visit(const FindFileAst *ffile) { if(!haveToFind(ffile->variableName())) return 1; if(m_cache->contains(ffile->variableName())) { qCDebug(CMAKE) << "FindFile: cache" << ffile->variableName(); return 1; } QStringList locationOptions = ffile->path()+ffile->hints(); if(!ffile->noDefaultPath()) { QStringList pp = envVarDirectories("CMAKE_PREFIX_PATH") + m_vars->value("CMAKE_PREFIX_PATH"); foreach(const QString& path, pp) { locationOptions += path+"/include"; } locationOptions += pp; locationOptions += envVarDirectories("CMAKE_INCLUDE_PATH") + m_vars->value("CMAKE_INCLUDE_PATH"); locationOptions += m_vars->value("CMAKE_FRAMEWORK_PATH"); pp=m_vars->value("CMAKE_SYSTEM_PREFIX_PATH"); foreach(const QString& path, pp) { locationOptions += path+"/include"; } locationOptions += m_vars->value("CMAKE_SYSTEM_INCLUDE_PATH"); locationOptions += m_vars->value("CMAKE_SYSTEM_FRAMEWORK_PATH"); } QStringList path, files=ffile->filenames(); qCDebug(CMAKE) << "Find File:" << ffile->filenames(); foreach(const QString& p, files) { QString p1=findFile(p, locationOptions, ffile->pathSuffixes()); if(p1.isEmpty()) { qCDebug(CMAKE) << p << "not found"; } else { path += p1; } } if(!path.isEmpty()) { m_vars->insertGlobal(ffile->variableName(), QStringList(path)); } else qCDebug(CMAKE) << "error. File" << ffile->filenames() << "not found"; // m_vars->insert(fpath->variableName()+"-NOTFOUND", QStringList()); return 1; } int CMakeProjectVisitor::visit(const TryCompileAst *tca) { qCDebug(CMAKE) << "try_compile" << tca->resultName() << tca->binDir() << tca->source() << "cmakeflags" << tca->cmakeFlags() << "outputvar" << tca->outputName(); if(m_projectName.isEmpty()) { qCDebug(CMAKE) << "file compile" << tca->compileDefinitions() << tca->copyFile(); } else { qCDebug(CMAKE) << "project compile" << tca->projectName() << tca->targetName(); } QString value; CacheValues::const_iterator it=m_cache->constFind(tca->resultName()); if(it!=m_cache->constEnd()) value=it->value; else value="TRUE"; m_vars->insert(tca->resultName(), QStringList(value)); return 1; } int CMakeProjectVisitor::visit(const TargetLinkLibrariesAst *tll) { qCDebug(CMAKE) << "target_link_libraries"; QHash::iterator target = m_targetForId.find(tll->target()); //TODO: we can add a problem if the target is not found if(target != m_targetForId.end()) { CategoryType& targetProps = m_props[TargetProperty]; CategoryType::iterator it = targetProps.find(m_targetAlias.value(tll->target(), tll->target())); (*it)["INTERFACE_LINK_LIBRARIES"] += tll->interfaceOnlyDependencies().retrieveTargets() << tll->publicDependencies().retrieveTargets(); (*it)["PRIVATE_LINK_LIBRARIES"] += tll->privateDependencies().retrieveTargets(); } return 1; } int CMakeProjectVisitor::visit(const TargetIncludeDirectoriesAst* tid) { CategoryType& targetProps = m_props[TargetProperty]; CategoryType::iterator it = targetProps.find(m_targetAlias.value(tid->target(), tid->target())); //TODO: we can add a problem if the target is not found if(it != targetProps.end()) { QStringList interfaceIncludes, includes; foreach(const TargetIncludeDirectoriesAst::Item& item, tid->items()) { if(item.visibility == TargetIncludeDirectoriesAst::Public || item.visibility == TargetIncludeDirectoriesAst::Interface) interfaceIncludes += item.item; if(item.visibility == TargetIncludeDirectoriesAst::Public || item.visibility == TargetIncludeDirectoriesAst::Private) includes += item.item; } if(!interfaceIncludes.isEmpty()) (*it)["INTERFACE_INCLUDE_DIRECTORIES"] += interfaceIncludes; if(!includes.isEmpty()) (*it)["INCLUDE_DIRECTORIES"] += includes; } return 1; } void CMakeProjectVisitor::macroDeclaration(const CMakeFunctionDesc& def, const CMakeFunctionDesc& end, const QStringList& args) { if(def.arguments.isEmpty() || end.arguments.isEmpty()) return; QString id=def.arguments.first().value.toLower(); Identifier identifier(id); RangeInRevision sr=def.arguments.first().range(); RangeInRevision endsr=end.arguments.first().range(); DUChainWriteLocker lock; QList decls=m_topctx->findDeclarations(identifier); //Only consider declarations in a CMake file IndexedString cmakeName("cmake"); for(QList::iterator it=decls.begin(); it!=decls.end(); ) { if((*it)->topContext()->parsingEnvironmentFile()->language() == cmakeName) ++it; else it = decls.erase(it); } int idx; if(!decls.isEmpty()) { idx=m_topctx->indexForUsedDeclaration(decls.first()); m_topctx->createUse(idx, sr, 0); } else { Declaration *d = new Declaration(sr, m_topctx); d->setIdentifier( identifier ); FunctionType* func=new FunctionType(); foreach(const QString& arg, args) { DelayedType *delayed=new DelayedType; delayed->setIdentifier( IndexedTypeIdentifier(arg) ); func->addArgument(AbstractType::Ptr(delayed)); } d->setAbstractType( AbstractType::Ptr(func) ); idx=m_topctx->indexForUsedDeclaration(d); } m_topctx->createUse(idx, endsr, 0); } int CMakeProjectVisitor::visit(const MacroAst *macro) { qCDebug(CMAKE) << "Adding macro:" << macro->macroName(); Macro m; m.name = macro->macroName(); m.knownArgs=macro->knownArgs(); m.isFunction=false; return declareFunction(m, macro->content(), macro->line(), "endmacro"); } int CMakeProjectVisitor::visit(const FunctionAst *func) { qCDebug(CMAKE) << "Adding function:" << func->name(); Macro m; m.name = func->name(); m.knownArgs=func->knownArgs(); m.isFunction=true; return declareFunction(m, func->content(), func->line(), "endfunction"); } int CMakeProjectVisitor::declareFunction(Macro m, const CMakeFileContent& content, int initial, const QString& end) { CMakeFileContent::const_iterator it=content.constBegin()+initial; CMakeFileContent::const_iterator itEnd=content.constEnd(); int lines=0; for(; it!=itEnd; ++it) { if(it->name.toLower()==end) break; m.code += *it; ++lines; } ++lines; //We do not want to return to endmacro if(it!=itEnd) { m_macros->insert(m.name, m); macroDeclaration(content[initial], content[initial+lines-1], m.knownArgs); } return lines; } int CMakeProjectVisitor::visit(const MacroCallAst *call) { if(m_macros->contains(call->name())) { const Macro code=m_macros->value(call->name()); qCDebug(CMAKE) << "Running macro:" << call->name() << "params:" << call->arguments() << "=" << code.knownArgs << "for" << code.code.count() << "lines"; if(code.knownArgs.count() > call->arguments().count()) { qCDebug(CMAKE) << "error: more parameters needed when calling" << call->name(); } else { { DUChainWriteLocker lock; QList decls=m_topctx->findDeclarations(Identifier(call->name().toLower())); if(!decls.isEmpty()) { int idx=m_topctx->indexForUsedDeclaration(decls.first()); m_topctx->createUse(idx, call->content()[call->line()].nameRange(), 0); } } //Giving value to parameters QStringList::const_iterator mit = code.knownArgs.constBegin(); QStringList::const_iterator cit = call->arguments().constBegin(); QStringList argn; int i=0; while(cit != call->arguments().constEnd()) { m_vars->insertMulti(QString("ARGV%1").arg(i), QStringList(*cit)); if(mit!=code.knownArgs.constEnd()) { qCDebug(CMAKE) << "param:" << *mit << "=" << *cit; m_vars->insertMulti(*mit, QStringList(*cit)); mit++; } else { argn += *cit; } cit++; i++; } m_vars->insertMulti("ARGN", argn); m_vars->insertMulti("ARGV", call->arguments()); m_vars->insertMulti("ARGC", QStringList(QString::number(call->arguments().count()))); qCDebug(CMAKE) << "argn=" << m_vars->value("ARGN"); bool isfunc = code.isFunction; if(isfunc) m_vars->pushScope(); //Executing int len = walk(code.code, 1); qCDebug(CMAKE) << "visited!" << call->name() << m_vars->value("ARGV") << "_" << m_vars->value("ARGN") << "..." << len; m_hitReturn = false; if(isfunc) m_vars->popScope(); //Restoring i=1; foreach(const QString& name, code.knownArgs) { m_vars->removeMulti(QString("ARGV%1").arg(i)); m_vars->removeMulti(name); i++; } m_vars->removeMulti("ARGV"); m_vars->removeMulti("ARGC"); m_vars->removeMulti("ARGN"); } } else { qCDebug(CMAKE) << "error: Did not find the macro:" << call->name() << call->content()[call->line()].writeBack(); } return 1; } static void usesForArguments(const QStringList& names, const QList& args, const ReferencedTopDUContext& topctx, const CMakeFunctionDesc& func) { //TODO: Should not return here if(args.size()!=names.size()) return; //We define the uses for the used variable without ${} foreach(int use, args) { QString var=names[use]; DUChainWriteLocker lock; QList decls=topctx->findDeclarations(Identifier(var)); if(!decls.isEmpty() && func.arguments.count() > use) { CMakeFunctionArgument arg=func.arguments[use]; int idx=topctx->indexForUsedDeclaration(decls.first()); topctx->createUse(idx, RangeInRevision(arg.line-1, arg.column-1, arg.line-1, arg.column-1+var.size()), 0); } } } int CMakeProjectVisitor::visit(const IfAst *ifast) //Highly crappy code { int lines=ifast->line(); if( ifast->condition().isEmpty() ) { const CMakeFunctionDesc d = ifast->content().at( ifast->line() ); qCDebug(CMAKE) << "error: couldn't parse condition of an IF in file:" << ifast->condition() << d.filePath << d.line; } int inside=0; bool visited=false; QList ini; for(; lines < ifast->content().size(); ++lines) { const CMakeFunctionDesc funcDesc = ifast->content().at(lines); QString funcName=funcDesc.name; // qCDebug(CMAKE) << "looking @" << lines << it->writeBack() << ">>" << inside << visited; if(funcName=="if") { inside++; } else if(funcName=="endif") { inside--; if(inside<=0) { // Q_ASSERT(!ini.isEmpty()); if(!funcDesc.arguments.isEmpty()) usesForArguments(ifast->condition(), ini, m_topctx, funcDesc); break; } // qCDebug(CMAKE) << "found an endif at:" << lines << "but" << inside; } if(inside==1) { bool result = false; if(funcName=="if" || funcName=="elseif") { CMakeCondition cond(this); IfAst myIf; QStringList condition; if(funcName=="if") { condition=ifast->condition(); } else { if(!myIf.parseFunctionInfo(resolveVariables(funcDesc))) qCDebug(CMAKE) << "uncorrect condition correct" << funcDesc.writeBack(); condition=myIf.condition(); } result=cond.condition(condition); int i=0; foreach(const QString& match, cond.matches()) { m_vars->insert(QString("CMAKE_MATCH_%1").arg(i), QStringList(match)); i++; } if(funcName=="if") ini=cond.variableArguments(); usesForArguments(condition, cond.variableArguments(), m_topctx, funcDesc); qCDebug(CMAKE) << ">> " << funcName << condition << result; } else if(funcName=="else") { qCDebug(CMAKE) << ">> else"; result=true; usesForArguments(ifast->condition(), ini, m_topctx, funcDesc); } if(!visited && result) { qCDebug(CMAKE) << "About to visit " << funcName << "?" << result; lines = walk(ifast->content(), lines+1)-1; visited=true; // qCDebug(CMAKE) << "Visited. now in" << it->name; } } } if(lines >= ifast->content().size()) { qCDebug(CMAKE) << "error. found an unfinished endif"; return ifast->content().size()-ifast->line(); } else { // qCDebug(CMAKE) << "finish" << "<>" << ifast->condition() << '|' << lines-ifast->line() << " to " << lines << '<' << ifast->content().size(); // qCDebug(CMAKE) << "endif==" << ifast->content()[lines].writeBack(); return lines-ifast->line()+1; } } int CMakeProjectVisitor::visit(const ExecProgramAst *exec) { QString execName = exec->executableName(); QStringList argsTemp = exec->arguments(); QStringList args; foreach(const QString& arg, argsTemp) { if(arg.contains("#[bin_dir]")) { if(!exec->outputVariable().isEmpty()) m_vars->insert(exec->outputVariable(), QStringList("OFF")); return 1; } args += arg.split(' '); } qCDebug(CMAKE) << "Executing:" << execName << "::" << args << "in" << exec->workingDirectory(); QProcess p; - KDevelop::restoreSystemEnvironment(&p); if(!exec->workingDirectory().isEmpty()) p.setWorkingDirectory(exec->workingDirectory()); p.setProcessChannelMode(QProcess::MergedChannels); p.setProgram(execName); p.setArguments(args); p.start(); if(!p.waitForFinished()) { qCDebug(CMAKE) << "error: failed to execute:" << execName << "error:" << p.error() << p.exitCode(); } if(!exec->returnValue().isEmpty()) { qCDebug(CMAKE) << "execution returned: " << exec->returnValue() << " = " << p.exitCode(); m_vars->insert(exec->returnValue(), QStringList(QString::number(p.exitCode()))); } if(!exec->outputVariable().isEmpty()) { QByteArray b = p.readAllStandardOutput(); QString t; t.prepend(b.trimmed()); m_vars->insert(exec->outputVariable(), QStringList(t.trimmed())); qCDebug(CMAKE) << "executed" << execName << "<" << t; } return 1; } int CMakeProjectVisitor::visit(const ExecuteProcessAst *exec) { qCDebug(CMAKE) << "executing... " << exec->commands(); QList procs; foreach(const QStringList& _args, exec->commands()) { if(_args.isEmpty()) { qCDebug(CMAKE) << "Error: trying to execute empty command"; break; } else { foreach(const QString& arg, _args) { if(arg.contains("#[bin_dir]")) { if(!exec->outputVariable().isEmpty()) m_vars->insert(exec->outputVariable(), QStringList("OFF")); return 1; } } } QString workingDir = exec->workingDirectory(); if(!QFile::exists(workingDir)) { workingDir = m_vars->value("CMAKE_CURRENT_BINARY_DIR").join(QString()); } QStringList args(_args); - QProcess *p = new QProcess(), *prev=0; - KDevPlatform::restoreSystemEnvironment(p); + QProcess *p=new QProcess(), *prev=0; if(!procs.isEmpty()) { prev=procs.last(); } p->setWorkingDirectory(workingDir); p->setProcessChannelMode(QProcess::MergedChannels); QString execName=args.takeFirst(); p->setProgram(execName); p->setArguments(args); p->start(); procs.append(p); qCDebug(CMAKE) << "Executing:" << execName << "::" << args /*<< "into" << *m_vars*/; if(prev) { prev->setStandardOutputProcess(p); } } foreach(QProcess* p, procs) { if(!p->waitForFinished()) { qCDebug(CMAKE) << "error: failed to execute:" << p; } } if(!procs.isEmpty() && !exec->resultVariable().isEmpty()) { qCDebug(CMAKE) << "execution returned: " << exec->resultVariable() << " = " << procs.last()->exitCode(); m_vars->insert(exec->resultVariable(), QStringList(QString::number(procs.last()->exitCode()))); } //FIXME: remove condition when filtering bad output if(!procs.isEmpty() && !exec->outputVariable().isEmpty()) { QByteArray b = procs.last()->readAllStandardOutput(); QString t; t.prepend(b.trimmed()); m_vars->insert(exec->outputVariable(), QStringList(t.trimmed().replace("\\", "\\\\"))); qCDebug(CMAKE) << "executed " << exec->outputVariable() << "=" << t; } qDeleteAll(procs); return 1; } int CMakeProjectVisitor::visit(const FileAst *file) { Q_ASSERT(m_vars->contains("CMAKE_CURRENT_SOURCE_DIR")); switch(file->type()) //TODO { case FileAst::Write: qCDebug(CMAKE) << "(ni) File write: " << file->path() << file->message(); break; case FileAst::Append: qCDebug(CMAKE) << "(ni) File append: " << file->path() << file->message(); break; case FileAst::Read: { QUrl filename =file->path(); QFileInfo ifile(filename.toLocalFile()); qCDebug(CMAKE) << "FileAst: reading " << file->path() << ifile.isFile(); if(!ifile.isFile()) return 1; QFile f(filename.toLocalFile()); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return 1; QString output=f.readAll(); m_vars->insert(file->variable(), QStringList(output)); qCDebug(CMAKE) << "FileAst: read "; } break; case FileAst::Glob: case FileAst::GlobRecurse: { QStringList matches; foreach(const QString& expr, file->globbingExpressions()) { if (expr.isEmpty()) continue; QString pathPrefix; if (QDir::isRelativePath(expr) && pathPrefix.isEmpty()) pathPrefix = m_vars->value("CMAKE_CURRENT_SOURCE_DIR").first(); matches.append(traverseGlob(pathPrefix, expr, file->type() == FileAst::GlobRecurse, file->isFollowingSymlinks())); } if (!file->path().isEmpty()) { // RELATIVE was specified, so we need to make all paths relative to file->path() QDir relative(file->path()); QStringList::iterator endIt = matches.end(); for(QStringList::iterator it = matches.begin(); it != endIt; ++it) { *it = relative.relativeFilePath(*it); } } m_vars->insert(file->variable(), matches); qCDebug(CMAKE) << "glob. recurse:" << (file->type() == FileAst::GlobRecurse) << "RELATIVE: " << file->path() << "FOLLOW_SYMLINKS: " << file->isFollowingSymlinks() << ", " << file->globbingExpressions() << ": " << matches; } break; case FileAst::Remove: case FileAst::RemoveRecurse: qCDebug(CMAKE) << "warning. file-remove or remove_recurse. KDevelop won't remove anything."; break; case FileAst::MakeDirectory: qCDebug(CMAKE) << "warning. file-make_directory. KDevelop won't create anything."; break; case FileAst::RelativePath: m_vars->insert(file->variable(), QStringList(QUrl::relativePath(file->directory(), file->path()))); qCDebug(CMAKE) << "file relative_path" << file->directory() << file->path(); break; case FileAst::ToCmakePath: #ifdef Q_OS_WIN m_vars->insert(file->variable(), file->path().replace("\\", "/").split(';')); #else m_vars->insert(file->variable(), file->path().split(':')); #endif qCDebug(CMAKE) << "file TO_CMAKE_PATH variable:" << file->variable() << "=" << m_vars->value(file->variable()) << "path:" << file->path(); break; case FileAst::ToNativePath: m_vars->insert(file->variable(), QStringList(file->path().replace('/', QDir::separator()))); qCDebug(CMAKE) << "file TO_NATIVE_PATH variable:" << file->variable() << "=" << m_vars->value(file->variable()) << "path:" << file->path(); break; case FileAst::Strings: { QUrl filename =file->path(); QFileInfo ifile(filename.toLocalFile()); qCDebug(CMAKE) << "FileAst: reading " << file->path() << ifile.isFile(); if(!ifile.isFile()) return 1; QFile f(filename.toLocalFile()); if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) return 1; QStringList output=QString(f.readAll()).split('\n'); if(!file->regex().isEmpty()) { QRegExp rx(file->regex()); for(QStringList::iterator it=output.begin(); it!=output.end(); ) { if(rx.indexIn(*it)>=0) ++it; else it = output.erase(it); } } m_vars->insert(file->variable(), output); } break; default: qCDebug(CMAKE) << "error: not implemented. file:" << file->type() << "variable:" << file->variable() << "file:" << file->path() << file->content()[file->line()].arguments[0].value; break; } return 1; } int CMakeProjectVisitor::visit(const MessageAst *msg) { s_msgcallback(msg->content().at(msg->line()).filePath+":"+QString::number(msg->line())+" "+msg->message().join(QString())); return 1; } int CMakeProjectVisitor::visit(const MathAst *math) { QScriptEngine eng; QScriptValue result = eng.evaluate(math->expression()); if (result.isError()) { qCDebug(CMAKE) << "error: found an error while calculating" << math->expression(); } qCDebug(CMAKE) << "math. " << math->expression() << "=" << result.toInteger(); m_vars->insert(math->outputVariable(), QStringList(QString::number(result.toInteger()))); return 1; } int CMakeProjectVisitor::visit(const GetFilenameComponentAst *filecomp) { Q_ASSERT(m_vars->contains("CMAKE_CURRENT_SOURCE_DIR")); QDir dir=m_vars->value("CMAKE_CURRENT_SOURCE_DIR").first(); QFileInfo fi(dir, filecomp->fileName()); QString val; switch(filecomp->type()) { case GetFilenameComponentAst::Path: { int idx = filecomp->fileName().lastIndexOf(QDir::separator()); if(idx>=0) val=filecomp->fileName().left(idx); } break; case GetFilenameComponentAst::RealPath: { val = fi.canonicalFilePath(); } break; case GetFilenameComponentAst::Absolute: val=fi.absoluteFilePath(); break; case GetFilenameComponentAst::Name: val=fi.fileName(); break; case GetFilenameComponentAst::Ext: val=fi.suffix(); break; case GetFilenameComponentAst::NameWe: val=fi.baseName(); break; case GetFilenameComponentAst::Program: qCDebug(CMAKE) << "error: filenamecopmonent PROGRAM not implemented"; //TODO: << break; } m_vars->insert(filecomp->variableName(), QStringList(val)); qCDebug(CMAKE) << "filename component" << filecomp->variableName() << "= " << filecomp->fileName() << "=" << val << endl; return 1; } int CMakeProjectVisitor::visit(const GetSourceFilePropAst* prop) { qCDebug(CMAKE) << "not supported yet :::" << prop->variableName(); m_vars->insert(prop->variableName(), QStringList()); return 1; } int CMakeProjectVisitor::visit(const OptionAst *opt) { qCDebug(CMAKE) << "option" << opt->variableName() << "-" << opt->description(); if(!m_vars->contains(opt->variableName()) && !m_cache->contains(opt->variableName())) { m_vars->insert(opt->variableName(), QStringList(opt->defaultValue())); } return 1; } int CMakeProjectVisitor::visit(const ListAst *list) { QString output = list->output(); QStringList theList = m_vars->value(list->list()); switch(list->type()) { case ListAst::Length: m_vars->insert(output, QStringList(QString::number(theList.count()))); qCDebug(CMAKE) << "List length" << m_vars->value(output); break; case ListAst::Get: { bool contains = m_vars->contains(list->list()); QStringList indices; if(contains) { foreach(int idx, list->index()) { if(idx>=theList.count() || (-idx)>theList.count()) qCDebug(CMAKE) << "error! trying to GET an element that doesn't exist!" << idx; else if(idx>=0) indices += theList[idx]; else indices += theList[theList.size()+idx]; } } else indices += "NOTFOUND"; m_vars->insert(output, indices); qCDebug(CMAKE) << "List: Get" << list->list() << theList << list->output() << list->index(); } break; case ListAst::Append: theList += list->elements(); m_vars->insert(list->list(), theList); break; case ListAst::Find: { QString element; int idx=-1; if(!list->elements().isEmpty()) { element = list->elements().first(); idx=theList.indexOf(element); } m_vars->insert(list->output(), QStringList(QString::number(idx))); qCDebug(CMAKE) << "List: Find" << theList << list->output() << list->elements() << idx; } break; case ListAst::Insert: { int p=list->index().first(); foreach(const QString& elem, list->elements()) { theList.insert(p >=0 ? p : (theList.size()+p), elem); p += p>=0? 1 : 0; } m_vars->insert(list->list(), theList); } break; case ListAst::RemoveItem: qCDebug(CMAKE) << "list remove item: " << theList << list->elements(); foreach(const QString& elem, list->elements()) { theList.removeAll(elem); } m_vars->insert(list->list(), theList); break; case ListAst::RemoveAt: { QList indices=list->index(); std::sort(indices.begin(), indices.end()); QList::const_iterator it=indices.constEnd(); qCDebug(CMAKE) << "list remove: " << theList << indices; do { --it; theList.removeAt(*it >= 0 ? *it : theList.size()+*it); } while(it!=indices.constBegin()); m_vars->insert(list->list(), theList); } break; case ListAst::Sort: std::sort(theList.begin(), theList.end()); m_vars->insert(list->list(), theList); break; case ListAst::Reverse: { QStringList reversed; foreach(const QString& elem, theList) reversed.prepend(elem); m_vars->insert(list->list(), reversed); } break; case ListAst::RemoveDuplicates: { QStringList noduplicates; foreach(const QString& elem, theList) { if(!noduplicates.contains(elem)) noduplicates.append(elem); } m_vars->insert(list->list(), noduplicates); } break; } qCDebug(CMAKE) << "List!!" << list->output() << '='<< m_vars->value(list->output()) << " -> " << m_vars->value(list->list()); return 1; } static int toCommandEnd(const CMakeAst* fea) { QString command = fea->content()[fea->line()].name; QString endCommand = "end"+command; int lines=fea->line()+1, depth=1; CMakeFileContent::const_iterator it=fea->content().constBegin()+lines; CMakeFileContent::const_iterator itEnd=fea->content().constEnd(); for(; depth>0 && it!=itEnd; ++it, lines++) { if(it->name==command) { depth++; } else if(it->name==endCommand) { depth--; } } return lines; } int CMakeProjectVisitor::visit(const ForeachAst *fea) { qCDebug(CMAKE) << "foreach>" << fea->loopVar() << "=" << fea->arguments() << "range=" << fea->type(); int end = -1; switch(fea->type()) { case ForeachAst::Range: for( int i = fea->ranges().start; i < fea->ranges().stop && !m_hitBreak; i += fea->ranges().step ) { m_vars->insertMulti(fea->loopVar(), QStringList(QString::number(i))); end=walk(fea->content(), fea->line()+1); m_vars->removeMulti(fea->loopVar()); if(m_hitBreak) break; } break; case ForeachAst::InItems: { QStringList args=fea->arguments(); foreach(const QString& s, args) { m_vars->insert(fea->loopVar(), QStringList(s)); qCDebug(CMAKE) << "looping" << fea->loopVar() << "=" << m_vars->value(fea->loopVar()); end=walk(fea->content(), fea->line()+1); if(m_hitBreak) break; } } break; case ForeachAst::InLists: { QStringList args=fea->arguments(); foreach(const QString& curr, args) { QStringList list = m_vars->value(curr); foreach(const QString& s, list) { m_vars->insert(fea->loopVar(), QStringList(s)); qCDebug(CMAKE) << "looping" << fea->loopVar() << "=" << m_vars->value(fea->loopVar()); end=walk(fea->content(), fea->line()+1); if(m_hitBreak) break; } } } break; } if(end<0) end = toCommandEnd(fea); else end++; m_hitBreak=false; qCDebug(CMAKE) << "EndForeach" << fea->loopVar(); return end-fea->line(); } int CMakeProjectVisitor::visit(const StringAst *sast) { qCDebug(CMAKE) << "String to" /*<< sast->input()*/ << sast->outputVariable(); switch(sast->type()) { case StringAst::Regex: { QStringList res; QRegExp rx(sast->regex()); rx.setPatternSyntax(QRegExp::RegExp2); QString totalInput = sast->input().join(QString()); switch(sast->cmdType()) { case StringAst::Match: { int match=rx.indexIn(totalInput); if(match>=0) { res = QStringList(totalInput.mid(match, rx.matchedLength())); break; } break; } case StringAst::MatchAll: { int pos = 0; while( (pos = rx.indexIn( totalInput, pos ) ) != -1 ) { res << rx.cap(); pos += rx.matchedLength(); } break; } case StringAst::RegexReplace: { foreach(const QString& in, sast->input()) { // QString() is required to get rid of the const res.append(QString(in).replace(rx, sast->replace())); } } break; default: qCDebug(CMAKE) << "ERROR String: Not a regex. " << sast->cmdType(); break; } for(int i=1; iinsert(QString("CMAKE_MATCH_%1").arg(i), QStringList(rx.cap(i))); } qCDebug(CMAKE) << "regex" << sast->outputVariable() << "=" << sast->regex() << res; m_vars->insert(sast->outputVariable(), res); } break; case StringAst::Replace: { QStringList out; foreach(const QString& _in, sast->input()) { QString in(_in); QString aux=in.replace(sast->regex(), sast->replace()); out += aux.split(';'); //FIXME: HUGE ugly hack } qCDebug(CMAKE) << "string REPLACE" << sast->input() << "=>" << out; m_vars->insert(sast->outputVariable(), out); } break; case StringAst::Compare: { QString res; switch(sast->cmdType()){ case StringAst::Equal: case StringAst::NotEqual: if(sast->input()[0]==sast->input()[1] && sast->cmdType()==StringAst::Equal) res = "TRUE"; else res = "FALSE"; break; case StringAst::Less: case StringAst::Greater: if(sast->input()[0]input()[1] && sast->cmdType()==StringAst::Less) res = "TRUE"; else res = "FALSE"; break; default: qCDebug(CMAKE) << "String: Not a compare. " << sast->cmdType(); } m_vars->insert(sast->outputVariable(), QStringList(res)); } break; case StringAst::Ascii: { QString res; foreach(const QString& ascii, sast->input()) { bool ok; res += QChar(ascii.toInt(&ok, 10)); } m_vars->insert(sast->outputVariable(), QStringList(res)); } break; case StringAst::Configure: //This is not up to the cmake support qCDebug(CMAKE) << "warning! String configure is not supported!" << sast->content()[sast->line()].writeBack(); break; case StringAst::ToUpper: m_vars->insert(sast->outputVariable(), QStringList(sast->input().join(QChar(';')).toUpper())); break; case StringAst::ToLower: m_vars->insert(sast->outputVariable(), QStringList(sast->input().join(QChar(';')).toLower())); break; case StringAst::Length: m_vars->insert(sast->outputVariable(), QStringList(QString::number(sast->input().join(QChar(';')).count()))); break; case StringAst::Substring: { QString res=sast->input().join(QString()); res=res.mid(sast->begin(), sast->length()); m_vars->insert(sast->outputVariable(), QStringList(res)); } break; case StringAst::Strip: m_vars->insert(sast->outputVariable(), QStringList( sast->string().trimmed() )); break; case StringAst::Random: { QString alphabet=sast->string(), result; for(int i=0; ilength(); i++) { int randv=qrand() % alphabet.size(); result += alphabet[randv]; } m_vars->insert(sast->outputVariable(), QStringList(result)); } break; } qCDebug(CMAKE) << "String " << m_vars->value(sast->outputVariable()); return 1; } int CMakeProjectVisitor::visit(const GetCMakePropertyAst *past) { QStringList output; switch(past->type()) { case GetCMakePropertyAst::Variables: qCDebug(CMAKE) << "get cmake prop: variables:" << m_vars->size(); output = m_vars->keys(); break; case GetCMakePropertyAst::CacheVariables: output = m_cache->keys(); break; case GetCMakePropertyAst::Components: case GetCMakePropertyAst::Commands: //FIXME: We do not have commands or components yet output = QStringList("NOTFOUND"); break; case GetCMakePropertyAst::Macros: output = m_macros->keys(); break; } m_vars->insert(past->variableName(), output); return 1; } int CMakeProjectVisitor::visit(const CustomCommandAst *ccast) { qCDebug(CMAKE) << "CustomCommand" << ccast->outputs(); if(ccast->isForTarget()) { //TODO: implement me } else { foreach(const QString& out, ccast->outputs()) { m_generatedFiles[out] = QStringList(ccast->mainDependency())/*+ccast->otherDependencies()*/; qCDebug(CMAKE) << "Have to generate:" << out << "with" << m_generatedFiles[out]; } } return 1; } int CMakeProjectVisitor::visit(const CustomTargetAst *ctar) { qCDebug(CMAKE) << "custom_target " << ctar->target() << ctar->dependencies() << ", " << ctar->commandArgs(); qCDebug(CMAKE) << ctar->content()[ctar->line()].writeBack(); defineTarget(ctar->target(), ctar->dependencies() + ctar->sourceLists(), Target::Custom); return 1; } int CMakeProjectVisitor::visit(const AddDefinitionsAst *addDef) { // qCDebug(CMAKE) << "Adding defs: " << addDef->definitions(); CMakeParserUtils::addDefinitions(addDef->definitions(), &m_defs, true); return 1; } int CMakeProjectVisitor::visit(const RemoveDefinitionsAst *remDef) { CMakeParserUtils::removeDefinitions(remDef->definitions(), &m_defs, true); return 1; } int CMakeProjectVisitor::visit(const MarkAsAdvancedAst *maa) { qCDebug(CMAKE) << "Mark As Advanced" << maa->advancedVars(); return 1; } /// @todo Add support for platform-specific argument splitting /// (UNIX_COMMAND and WINDOWS_COMMAND) introduced in CMake 2.8. int CMakeProjectVisitor::visit( const SeparateArgumentsAst * separgs ) { QString varName=separgs->variableName(); QStringList res; foreach(const QString& value, m_vars->value(varName)) { res += value.split(' '); } m_vars->insert(varName, res); return 1; } int CMakeProjectVisitor::visit(const SetPropertyAst* setp) { QStringList args = setp->args(); switch(setp->type()) { case GlobalProperty: args = QStringList() << QString(); break; case DirectoryProperty: args = m_vars->value("CMAKE_CURRENT_SOURCE_DIR"); break; default: break; } qCDebug(CMAKE) << "setprops" << setp->type() << args << setp->name() << setp->values(); CategoryType& cm=m_props[setp->type()]; if(setp->append()) { foreach(const QString &it, args) { cm[it][setp->name()].append(setp->values()); } } else if (setp->appendString()) { const QString toAppend = setp->values().join(QString()); foreach(const QString &it, args) { QStringList& values = cm[it][setp->name()]; if (values.isEmpty()) { values.append(toAppend); } else { values.last().append(toAppend); } } } else { foreach(const QString &it, args) cm[it].insert(setp->name(), setp->values()); } return 1; } int CMakeProjectVisitor::visit(const GetPropertyAst* getp) { QStringList retv; if(getp->type() == CacheProperty) { retv = m_cache->value(getp->typeName()).value.split(':'); } else { QString catn; switch(getp->type()) { case GlobalProperty: break; case DirectoryProperty: catn = getp->typeName(); if(catn.isEmpty()) catn = m_vars->value("CMAKE_CURRENT_SOURCE_DIR").join(QString()); break; default: catn = getp->typeName(); break; } retv = m_props[getp->type()][catn][getp->name()]; } m_vars->insert(getp->outputVariable(), retv); qCDebug(CMAKE) << "getprops" << getp->type() << getp->name() << getp->typeName() << getp->outputVariable() << "=" << retv; return 1; } int CMakeProjectVisitor::visit(const GetDirPropertyAst* getdp) { qCDebug(CMAKE) << "getprops"; QStringList retv; QString dir=getdp->directory(); if(dir.isEmpty()) { dir=m_vars->value("CMAKE_CURRENT_SOURCE_DIR").join(QString()); } else if(QUrl::isRelativeUrl(dir)) { QUrl u(m_vars->value("CMAKE_CURRENT_SOURCE_DIR").join(QString())); u = u.adjusted(QUrl::StripTrailingSlash); u.setPath(u.path() + '/' + dir); dir=u.path(); } retv=m_props[DirectoryProperty][dir][getdp->propName()]; m_vars->insert(getdp->outputVariable(), retv); return 1; } int CMakeProjectVisitor::visit(const SetTestsPropsAst* stp) { QHash props; foreach(const SetTestsPropsAst::PropPair& property, stp->properties()) { props.insert(property.first, property.second); } for(QVector::iterator it=m_testSuites.begin(), itEnd=m_testSuites.end(); it!=itEnd; ++it) { it->properties = props; } return 1; } int CMakeProjectVisitor::visit( const WhileAst * whileast) { CMakeCondition cond(this); bool result=cond.condition(whileast->condition()); usesForArguments(whileast->condition(), cond.variableArguments(), m_topctx, whileast->content()[whileast->line()]); qCDebug(CMAKE) << "Visiting While" << whileast->condition() << "?" << result; int end = toCommandEnd(whileast); if(endcontent().size()) { usesForArguments(whileast->condition(), cond.variableArguments(), m_topctx, whileast->content()[end]); if(result) { walk(whileast->content(), whileast->line()+1); if(m_hitBreak) { qCDebug(CMAKE) << "break found. leaving loop"; m_hitBreak=false; } else walk(whileast->content(), whileast->line()); } } qCDebug(CMAKE) << "endwhile" << whileast->condition() /*<< whileast->content()[end]*/; return end-whileast->line(); } CMakeFunctionDesc CMakeProjectVisitor::resolveVariables(const CMakeFunctionDesc & exp) { CMakeFunctionDesc ret=exp; ret.arguments.clear(); foreach(const CMakeFunctionArgument &arg, exp.arguments) { if(arg.value.contains('$')) ret.addArguments(resolveVariable(arg), arg.quoted); else ret.arguments.append(arg); } return ret; } enum RecursivityType { No, Yes, End, Break, Return }; static RecursivityType recursivity(const QString& functionName) { QString upperFunctioName=functionName; if(upperFunctioName=="if" || upperFunctioName=="while" || upperFunctioName=="foreach" || upperFunctioName=="macro") return Yes; else if(upperFunctioName=="else" || upperFunctioName=="elseif" || upperFunctioName.startsWith("end")) return End; else if(upperFunctioName=="break") return Break; else if(upperFunctioName=="return") return Return; return No; } int CMakeProjectVisitor::walk(const CMakeFileContent & fc, int line, bool isClean) { if(fc.isEmpty()) return 0; ReferencedTopDUContext aux=m_topctx; IndexedString url(fc.first().filePath); if(!m_topctx || m_topctx->url()!=url) { qCDebug(CMAKE) << "Creating a context for" << url; m_topctx=createContext(url, aux ? aux : m_parentCtx, fc.last().endLine-1, fc.last().endColumn-1, isClean); if(!aux) aux=m_topctx; } VisitorState p; p.code = &fc; p.context = m_topctx; p.line = line; m_backtrace.push(p); CMakeFileContent::const_iterator it=fc.constBegin()+line, itEnd=fc.constEnd(); for(; it!=itEnd; ) { Q_ASSERT( line=0 ); Q_ASSERT( *it == fc[line] ); RecursivityType r = recursivity(it->name); if(r==End || r==Break || r==Return || m_hitBreak || m_hitReturn) { m_backtrace.pop(); m_topctx=aux; if(r==Break) m_hitBreak=true; if(r==Return) m_hitReturn=true; return line; } CMakeAst* element = AstFactory::self()->createAst(it->name); createUses(*it); // qCDebug(CMAKE) << "resolving:" << it->writeBack(); CMakeFunctionDesc func = resolveVariables(*it); //FIXME not correct in while case bool correct = element->parseFunctionInfo(func); // qCDebug(CMAKE) << "resolved:" << func.writeBack() << correct; if(!correct) { qCDebug(CMAKE) << "error! found an error while processing" << func.writeBack() << "was" << it->writeBack() << endl << " at" << url << ":" << func.line << endl; //FIXME: Should avoid to run? } if(element->isDeprecated()) { qCDebug(CMAKE) << "Warning: Using the function: " << func.name << " which is deprecated by cmake."; DUChainWriteLocker lock(DUChain::lock()); Problem::Ptr p(new Problem); p->setDescription(i18n("%1 is a deprecated command and should not be used", func.name)); p->setRange(it->nameRange()); p->setFinalLocation(DocumentRange(url, it->nameRange().castToSimpleRange())); m_topctx->addProblem(p); } element->setContent(fc, line); createDefinitions(element); m_vars->insertGlobal("CMAKE_CURRENT_LIST_LINE", QStringList(QString::number(it->line))); int lines=element->accept(this); line+=lines; m_backtrace.top().line = line; m_backtrace.top().context = m_topctx; delete element; if(line>fc.count()) { Problem::Ptr p(new Problem); p->setDescription(i18n("Unfinished function. ")); p->setRange(it->nameRange()); p->setFinalLocation(DocumentRange(url, KDevelop::RangeInRevision(fc.first().range().start, fc.last().range().end).castToSimpleRange())); DUChainWriteLocker lock(DUChain::lock()); m_topctx->addProblem(p); break; } it+=lines; } m_backtrace.pop(); m_topctx=aux; qCDebug(CMAKE) << "Walk stopped @" << line; return line; } void CMakeProjectVisitor::createDefinitions(const CMakeAst *ast) { if(!m_topctx) return; foreach(const CMakeFunctionArgument &arg, ast->outputArguments()) { if(!arg.isCorrect()) continue; Identifier id(arg.value); DUChainWriteLocker lock; QList decls=m_topctx->findDeclarations(id); if(decls.isEmpty()) { Declaration *d = new Declaration(arg.range(), m_topctx); d->setIdentifier(id); } else { int idx=m_topctx->indexForUsedDeclaration(decls.first()); m_topctx->createUse(idx, arg.range(), 0); } } } void CMakeProjectVisitor::createUses(const CMakeFunctionDesc& desc) { if(!m_topctx) return; foreach(const CMakeFunctionArgument &arg, desc.arguments) { if(!arg.isCorrect() || !arg.value.contains('$')) continue; QList var = parseArgument(arg.value); QList::const_iterator it, itEnd=var.constEnd(); for(it=var.constBegin(); it!=itEnd; ++it) { QString var=arg.value.mid(it->first+1, it->second-it->first-1); DUChainWriteLocker lock; QList decls=m_topctx->findDeclarations(Identifier(var)); if(!decls.isEmpty()) { int idx=m_topctx->indexForUsedDeclaration(decls.first()); m_topctx->createUse(idx, RangeInRevision(arg.line-1, arg.column+it->first, arg.line-1, arg.column+it->second-1), 0); } } } } void CMakeProjectVisitor::setCacheValues( CacheValues* cache) { m_cache=cache; } void CMakeProjectVisitor::setVariableMap(VariableMap * vars) { m_vars=vars; } QStringList CMakeProjectVisitor::dependees(const QString& s) const { QStringList ret; if(isGenerated(s)) { foreach(const QString& f, m_generatedFiles[s]) ret += dependees(f); } else { ret += s; } return ret; } QStringList CMakeProjectVisitor::resolveDependencies(const QStringList & files) const { QStringList ret; foreach(const QString& s, files) { if(isGenerated(s)) { qCDebug(CMAKE) << "Generated:" << s; QStringList gen = dependees(s); foreach(const QString& file, gen) { if(!ret.contains(file)) ret.append(file); } } else { ret.append(s); } } return ret; } QStringList CMakeProjectVisitor::traverseGlob(const QString& startPath, const QString& expression, bool recursive, bool followSymlinks) { qCDebug(CMAKE) << "Starting from (" << startPath << ", " << expression << "," << recursive << ", " << followSymlinks << ")"; QString expr = expression; int firstSlash = expr.indexOf('/'); int slashShift = 0; while (firstSlash == slashShift) //skip trailing slashes { slashShift++; firstSlash = expr.indexOf('/', slashShift); } expr = expr.mid(slashShift); if (firstSlash == -1) { //We're in place. Lets match files from startPath dir. qCDebug(CMAKE) << "Matching files in " << startPath << " with glob " << expr; QStringList dirsToSearch; if (recursive) { QDir::Filters dirFilters = QDir::NoDotAndDotDot | QDir::Dirs; bool CMP0009IsSetToNew = true; // TODO: Obey CMP0009 policy when policies are implemented. if (!(CMP0009IsSetToNew && followSymlinks)) dirFilters |= QDir::NoSymLinks; QQueue dirsToExpand; dirsToExpand.enqueue(startPath); while (!dirsToExpand.empty()) { QString dir = dirsToExpand.dequeue(); qCDebug(CMAKE) << "Enqueueing " << dir; dirsToSearch << dir; QDir d(dir); QStringList dirNames = d.entryList(dirFilters); foreach(const QString& dirName, dirNames) { dirsToExpand << d.filePath(dirName); } } } else dirsToSearch << startPath; QStringList filePaths; const QStringList nameFilters(expr); foreach (const QString& dirToSearch, dirsToSearch) { QDir dir(dirToSearch); QStringList fileNames = dir.entryList(nameFilters, QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot); foreach (const QString& fileName, fileNames) { filePaths << dir.filePath(fileName); } } return filePaths; } firstSlash -= slashShift; QString dirGlob = expr.left(firstSlash); QString rightExpression = expr.mid(firstSlash + 1); //Now we must find match for a directory specified in dirGlob QStringList matchedDirs; if (dirGlob.contains('*') || dirGlob.contains('?') || dirGlob.contains('[')) { qCDebug(CMAKE) << "Got a dir glob " << dirGlob; if (startPath.isEmpty()) return QStringList(); //it's really a glob, not just dir name matchedDirs = QDir(startPath).entryList(QStringList(dirGlob), QDir::NoDotAndDotDot | QDir::Dirs); } else { //just a directory name. Add it as a match. qCDebug(CMAKE) << "Got a simple folder " << dirGlob; matchedDirs << dirGlob; } QStringList matches; QString path = startPath; if (!path.endsWith('/')) path += '/'; foreach(const QString& dirName, matchedDirs) { qCDebug(CMAKE) << "Going recursive into " << path + dirName << " and glob " << rightExpression; matches.append(traverseGlob(path + dirName, rightExpression, recursive, followSymlinks)); } return matches; } diff --git a/projectmanagers/cmake/tests/cmakecompliance.cpp b/projectmanagers/cmake/tests/cmakecompliance.cpp index 369a648505..e9ebfbd918 100644 --- a/projectmanagers/cmake/tests/cmakecompliance.cpp +++ b/projectmanagers/cmake/tests/cmakecompliance.cpp @@ -1,181 +1,179 @@ /* KDevelop CMake Support * * Copyright 2007 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 "cmakecompliance.h" #include "cmakeast.h" #include "cmakeprojectvisitor.h" #include "astfactory.h" #include "cmake-test-paths.h" #include "../debug.h" #include #include #include #include #include #include -#include #include using namespace KDevelop; QTEST_MAIN( CMakeCompliance ) QString CMakeCompliance::output; //Copied from CMakeManager QString executeProcess(const QString& execName, const QStringList& args=QStringList()) { QProcess p; - KDevelop::restoreSystemEnvironment(&p); p.setProcessChannelMode(QProcess::MergedChannels); p.setProgram(execName, args); p.start(); if(!p.waitForFinished()) { qCDebug(CMAKE) << "failed to execute:" << execName; } QByteArray b = p.readAllStandardOutput(); QString t; t.prepend(b.trimmed()); return t; } void CMakeCompliance::testEnumerate() { QFETCH( QString, exe); QStringList commands=executeProcess(exe, QStringList("--help-command-list")).split("\n"); commands.erase(commands.begin()); commands.sort(); foreach(const QString& cmd, commands) { if(cmd.toLower().startsWith("end") || cmd.toLower()=="else" || cmd.toLower()=="elseif") continue; CMakeAst* element = AstFactory::self()->createAst(cmd); if(!element) qDebug() << cmd << "is not supported"; delete element; } } void CMakeCompliance::testEnumerate_data() { QTest::addColumn( "exe" ); QStringList cmakes; KStandardDirs::findAllExe(cmakes, "cmake"); foreach(const QString& path, cmakes) { QTest::newRow( qPrintable(path) ) << (path); } } CMakeProjectVisitor CMakeCompliance::parseFile( const QString& sourcefile ) { CMakeProjectVisitor::setMessageCallback(CMakeCompliance::addOutput); QString projectfile = sourcefile; CMakeFileContent code=CMakeListsParser::readCMakeFile(projectfile); static QPair initials = CMakeParserUtils::initialVariables(); CMakeProjectData data; data.vm = initials.first; QString sourcedir=sourcefile.left(sourcefile.lastIndexOf('/')); data.vm.insert("CMAKE_SOURCE_DIR", QStringList(sourcedir)); KDevelop::ReferencedTopDUContext buildstrapContext=new TopDUContext(IndexedString("buildstrap"), RangeInRevision(0,0, 0,0)); DUChain::self()->addDocumentChain(buildstrapContext); ReferencedTopDUContext ref=buildstrapContext; QStringList modulesPath = data.vm["CMAKE_MODULE_PATH"]; foreach(const QString& script, initials.second) { ref = CMakeParserUtils::includeScript(CMakeProjectVisitor::findFile(script, modulesPath, QStringList()), ref, &data, sourcedir, QMap()); } data.vm.insert("CMAKE_CURRENT_BINARY_DIR", QStringList(sourcedir)); data.vm.insert("CMAKE_CURRENT_LIST_FILE", QStringList(projectfile)); data.vm.insert("CMAKE_CURRENT_SOURCE_DIR", QStringList(sourcedir)); CMakeProjectVisitor v(projectfile, ref); v.setVariableMap(&data.vm); v.setMacroMap(&data.mm); v.setCacheValues(&data.cache); v.setModulePath(modulesPath); output.clear(); v.walk(code, 0); ReferencedTopDUContext ctx=v.context(); return v; } void CMakeCompliance::testCMakeTests() { QFETCH(QString, exe); QFETCH(QString, file); CMakeProjectVisitor v = parseFile(file); QString ret=executeProcess(exe, QStringList("-P") << file); QStringList outputList = output.split('\n'), cmakeList=ret.split('\n'); for(int i=0; i("exe"); QTest::addColumn("file"); QStringList files=QStringList() // << "/CMakeTests/IfTest.cmake.in" << "/CMakeTests/ListTest.cmake.in" ; QStringList cmakes; KStandardDirs::findAllExe(cmakes, "cmake"); foreach(const QString& exe, cmakes) { foreach(const QString& file, files) QTest::newRow( qPrintable(QString(exe+file)) ) << exe << QString(CMAKE_TESTS_PROJECTS_DIR + file); } } void CMakeCompliance::addOutput(const QString& msg) { output += "-- "+msg+'\n'; } CMakeCompliance::CMakeCompliance() { AutoTestShell::init(); KDevelop::TestCore::initialize( Core::NoUi ); } CMakeCompliance::~CMakeCompliance() { KDevelop::TestCore::shutdown(); } diff --git a/projectmanagers/cmake/tests/test_cmakemanager.cpp b/projectmanagers/cmake/tests/test_cmakemanager.cpp index 9a64b7ca3b..922e22fbea 100644 --- a/projectmanagers/cmake/tests/test_cmakemanager.cpp +++ b/projectmanagers/cmake/tests/test_cmakemanager.cpp @@ -1,347 +1,349 @@ /* This file is part of KDevelop Copyright 2010 Esben Mose Hansen 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. */ #include "test_cmakemanager.h" #include "testhelpers.h" #include "cmakemodelitems.h" #include "cmakeutils.h" #include "cmakeimportjsonjob.h" #include #include #include #include #include #include #include #include #include QTEST_MAIN(TestCMakeManager) using namespace KDevelop; void TestCMakeManager::initTestCase() { + QLoggingCategory::setFilterRules(QStringLiteral("*.debug=false\ndefault.debug=true\nkdevelop.projectmanagers.cmake.debug=true\n")); + AutoTestShell::init(); TestCore::initialize(); cleanup(); } void TestCMakeManager::cleanupTestCase() { TestCore::shutdown(); } void TestCMakeManager::cleanup() { foreach(IProject* p, ICore::self()->projectController()->projects()) { ICore::self()->projectController()->closeProject(p); } QVERIFY(ICore::self()->projectController()->projects().isEmpty()); } void TestCMakeManager::testWithBuildDirProject() { loadProject("with_build_dir"); } void TestCMakeManager::testIncludePaths() { IProject* project = loadProject("single_subdirectory"); Path sourceDir = project->path(); Path fooCpp(sourceDir, "subdir/foo.cpp"); QVERIFY(QFile::exists(fooCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(fooCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QCOMPARE(items.size(), 2); // once the target, once the plain file ProjectBaseItem* fooCppItem = items.first(); Path::List includeDirs = project->buildSystemManager()->includeDirectories(fooCppItem); QVERIFY(includeDirs.size() >= 3); Path buildDir(sourceDir, "build/"); QVERIFY(includeDirs.contains(buildDir)); Path subBuildDir(sourceDir, "build/subdir/"); QVERIFY(includeDirs.contains(subBuildDir)); Path subDir(sourceDir, "subdir/"); QVERIFY(includeDirs.contains(subDir)); } void TestCMakeManager::testRelativePaths() { IProject* project = loadProject("relative_paths", "/out"); Path codeCpp(project->path(), "../src/code.cpp"); QVERIFY(QFile::exists( codeCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(codeCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Abort); QCOMPARE(items.size(), 1); // once in the target ProjectBaseItem* fooCppItem = items.first(); Path::List includeDirs = project->buildSystemManager()->includeDirectories(fooCppItem); Path incDir(project->path(), "../inc/"); QVERIFY(includeDirs.contains( incDir )); } void TestCMakeManager::testTargetIncludePaths() { IProject* project = loadProject("target_includes"); Path mainCpp(project->path(), "main.cpp"); QVERIFY(QFile::exists(mainCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QCOMPARE(items.size(), 2); // once the plain file, once the target bool foundInTarget = false; foreach(ProjectBaseItem* mainCppItem, items) { ProjectBaseItem* mainContainer = mainCppItem->parent(); Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem); if (mainContainer->target()) { foundInTarget = true; Path targetIncludesDir(project->path(), "includes/"); QVERIFY(includeDirs.contains(targetIncludesDir)); } } QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QVERIFY(foundInTarget); } void TestCMakeManager::testTargetIncludeDirectories() { IProject* project = loadProject("target_include_directories"); Path mainCpp(project->path(), "main.cpp"); QVERIFY(QFile::exists(mainCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QCOMPARE(items.size(), 2); // once the plain file, once the target bool foundInTarget = false; foreach(ProjectBaseItem* mainCppItem, items) { ProjectBaseItem* mainContainer = mainCppItem->parent(); Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem); if (mainContainer->target()) { foundInTarget = true; QVERIFY(includeDirs.contains(Path(project->path(), "includes/"))); QVERIFY(includeDirs.contains(Path(project->path(), "libincludes/"))); } } QEXPECT_FAIL("", "files aren't being added to the target", Continue); QVERIFY(foundInTarget); } void TestCMakeManager::testQt5App() { IProject* project = loadProject("qt5_app"); Path mainCpp(project->path(), "main.cpp"); QVERIFY(QFile::exists(mainCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QCOMPARE(items.size(), 2); // once the plain file, once the target bool foundCore = false, foundGui = false, foundWidgets = false; foreach(ProjectBaseItem* mainCppItem, items) { Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem); foreach(const Path& include, includeDirs) { QString filename = include.lastPathSegment(); foundCore |= filename == "QtCore"; foundGui |= filename == "QtGui"; foundWidgets |= filename == "QtWidgets"; } } QVERIFY(foundCore); QVERIFY(foundGui); QVERIFY(foundWidgets); } void TestCMakeManager::testQt5AppOld() { IProject* project = loadProject("qt5_app_old"); Path mainCpp(project->path(), "main.cpp"); QVERIFY(QFile::exists(mainCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QCOMPARE(items.size(), 2); // once the plain file, once the target bool foundCore = false, foundGui = false, foundWidgets = false; foreach(ProjectBaseItem* mainCppItem, items) { Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem); foreach(const Path& include, includeDirs) { QString filename = include.lastPathSegment(); foundCore |= filename == "QtCore"; foundGui |= filename == "QtGui"; foundWidgets |= filename == "QtWidgets"; } } QVERIFY(foundCore); QVERIFY(foundGui); QVERIFY(foundWidgets); } void TestCMakeManager::testKF5App() { IProject* project = loadProject("kf5_app"); Path mainCpp(project->path(), "main.cpp"); QVERIFY(QFile::exists(mainCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QCOMPARE(items.size(), 2); // once the plain file, once the target bool foundCore = false, foundGui = false, foundWidgets = false, foundWidgetsAddons = false; foreach(ProjectBaseItem* mainCppItem, items) { Path::List includeDirs = project->buildSystemManager()->includeDirectories(mainCppItem); qDebug() << "xxxxxxxxx" << includeDirs; foreach(const Path& include, includeDirs) { QString filename = include.lastPathSegment(); foundCore |= filename == "QtCore"; foundGui |= filename == "QtGui"; foundWidgets |= filename == "QtWidgets"; foundWidgetsAddons |= filename == "KWidgetsAddons"; } } QVERIFY(foundCore); QVERIFY(foundGui); QVERIFY(foundWidgets); QVERIFY(foundWidgetsAddons); } void TestCMakeManager::testDefines() { IProject* project = loadProject("defines"); Path mainCpp(project->path(), "main.cpp"); QVERIFY(QFile::exists(mainCpp.toLocalFile())); QList< ProjectBaseItem* > items = project->itemsForPath(IndexedString(mainCpp.pathOrUrl())); QEXPECT_FAIL("", "Will fix soon, hopefully", Continue); QCOMPARE(items.size(), 2); // once the plain file, once the target bool foundInTarget = false; foreach(ProjectBaseItem* mainCppItem, items) { QHash defines = project->buildSystemManager()->defines(mainCppItem); QCOMPARE(defines.value("B", QStringLiteral("not found")), QString()); QCOMPARE(defines.value("BV", QStringLiteral("not found")), QStringLiteral("1")); QCOMPARE(defines.value("BV2", QStringLiteral("not found")), QStringLiteral("2")); // QCOMPARE(defines.value("BAR", QStringLiteral("not found")), QStringLiteral("foo")); // QCOMPARE(defines.value("FOO", QStringLiteral("not found")), QStringLiteral("bar")); // QCOMPARE(defines.value("BLA", QStringLiteral("not found")), QStringLiteral("blub")); QCOMPARE(defines.value("ASDF", QStringLiteral("not found")), QStringLiteral("asdf")); QCOMPARE(defines.value("XYZ", QStringLiteral("not found")), QString()); QCOMPARE(defines.value("A", QStringLiteral("not found")), QString()); QCOMPARE(defines.value("AV", QStringLiteral("not found")), QStringLiteral("1")); QCOMPARE(defines.value("AV2", QStringLiteral("not found")), QStringLiteral("2")); QCOMPARE(defines.value("C", QStringLiteral("not found")), QString()); QCOMPARE(defines.value("CV", QStringLiteral("not found")), QStringLiteral("1")); QCOMPARE(defines.value("CV2", QStringLiteral("not found")), QStringLiteral("2")); QCOMPARE(defines.size(), 13); foundInTarget = true; } QVERIFY(foundInTarget); } void TestCMakeManager::testCustomTargetSources() { IProject* project = loadProject("custom_target_sources"); QEXPECT_FAIL("", "Will fix soon, hopefully", Abort); QList targets = project->buildSystemManager()->targets(project->projectItem()); QVERIFY(targets.size() == 1); ProjectTargetItem *target = targets.first(); QCOMPARE(target->fileList().size(), 1); QCOMPARE(target->fileList().first()->baseName(), QStringLiteral("foo.cpp")); } void TestCMakeManager::testConditionsInSubdirectoryBasedOnRootVariables() { IProject* project = loadProject("conditions_in_subdirectory_based_on_root_variables"); Path rootFooCpp(project->path(), "foo.cpp"); QVERIFY(QFile::exists(rootFooCpp.toLocalFile())); QList< ProjectBaseItem* > rootFooItems = project->itemsForPath(IndexedString(rootFooCpp.pathOrUrl())); QEXPECT_FAIL("", "files aren't being added to the target", Continue); QCOMPARE(rootFooItems.size(), 4); // three items for the targets, one item for the plain file Path subdirectoryFooCpp(project->path(), "subdirectory/foo.cpp"); QVERIFY(QFile::exists(subdirectoryFooCpp.toLocalFile())); QList< ProjectBaseItem* > subdirectoryFooItems = project->itemsForPath(IndexedString(subdirectoryFooCpp.pathOrUrl())); QEXPECT_FAIL("", "files aren't being added to the target", Continue); QCOMPARE(subdirectoryFooItems.size(), 4); // three items for the targets, one item for the plain file } void TestCMakeManager::testEnumerateTargets() { QString tempDir = QDir::tempPath(); QTemporaryFile targetDirectoriesFile; QTemporaryDir subdir; auto opened = targetDirectoriesFile.open(); QVERIFY(opened); QVERIFY(subdir.isValid()); const QString targetDirectoriesContent = tempDir + "/CMakeFiles/first_target.dir\n" + tempDir + "/CMakeFiles/second_target.dir\r\n" + tempDir + "/" + subdir.path() + "/CMakeFiles/third_target.dir"; targetDirectoriesFile.write(targetDirectoriesContent.toLatin1()); targetDirectoriesFile.close(); QHash targets = CMake::enumerateTargets(Path(targetDirectoriesFile.fileName()), tempDir, Path(tempDir)); QCOMPARE(targets.value(Path(tempDir)).value(0), QStringLiteral("first_target")); QCOMPARE(targets.value(Path(tempDir)).value(1), QStringLiteral("second_target")); QCOMPARE(targets.value(Path(tempDir + "/" + subdir.path())).value(0), QStringLiteral("third_target")); } void TestCMakeManager::testFaultyTarget() { loadProject("faulty_target"); } void TestCMakeManager::testParenthesesInTestArguments() { IProject* project = loadProject("parentheses_in_test_arguments"); Path sourceDir = project->path(); Path buildDir(sourceDir, "build/"); CMakeImportJob* job = new CMakeImportJob(project, this); job->start(); } diff --git a/projectmanagers/qmake/qmakejob.cpp b/projectmanagers/qmake/qmakejob.cpp index dfaf753de0..f56ba3bf82 100644 --- a/projectmanagers/qmake/qmakejob.cpp +++ b/projectmanagers/qmake/qmakejob.cpp @@ -1,163 +1,161 @@ /*************************************************************************** * This file is part of KDevelop * * Copyright 2010 Milian Wolff * * * * This program 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 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 Library 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 "qmakejob.h" #include #include #include #include #include #include #include -#include #include #include #include "debug.h" using namespace KDevelop; QMakeJob::QMakeJob(QString srcDir, QString buildDir, QObject* parent) : OutputJob(parent) , m_srcDir(std::move(srcDir)) , m_buildDir(std::move(buildDir)) , m_qmakePath("qmake") , m_buildType(0) , m_process(nullptr) , m_model(nullptr) { setCapabilities(Killable); setStandardToolView(IOutputView::RunView); setBehaviours(IOutputView::AllowUserClose | IOutputView::AutoScroll); setObjectName(i18n("Run QMake in %1", m_buildDir)); } QMakeJob::~QMakeJob() { } void QMakeJob::setQMakePath(const QString& path) { m_qmakePath = path; } void QMakeJob::setInstallPrefix(const QString& prefix) { m_installPrefix = prefix; } void QMakeJob::setBuildType(int comboboxSelectedIndex) { m_buildType = comboboxSelectedIndex; } void QMakeJob::setExtraArguments(const QString& args) { m_extraArguments = args; } void QMakeJob::start() { static const char* BUILD_TYPES[] = { "debug", "build", "(don't specify)" }; m_model = new OutputModel; setModel(m_model); startOutput(); QStringList args; if (m_buildType < 2) args << QString("CONFIG+=") + BUILD_TYPES[m_buildType]; if (!m_installPrefix.isEmpty()) args << "target.path=" + m_installPrefix; if (!m_extraArguments.isEmpty()) { KShell::Errors err; QStringList tmp = KShell::splitArgs(m_extraArguments, KShell::TildeExpand | KShell::AbortOnMeta, &err); if (err == KShell::NoError) { args += tmp; } else { qCWarning(KDEV_QMAKE) << "Ignoring qmake Extra arguments"; if (err == KShell::BadQuoting) { qCWarning(KDEV_QMAKE) << "QMake arguments badly quoted:" << m_extraArguments; } else { qCWarning(KDEV_QMAKE) << "QMake arguments had meta character:" << m_extraArguments; } } } args << "-r" << m_srcDir; m_model->appendLine(m_buildDir + ": " + args.join(" ")); QDir build(m_buildDir); if (!build.exists()) { build.mkpath(build.absolutePath()); } m_process = new QProcess(this); - KDevelop::restoreSystemEnvironment(m_process); m_process->setWorkingDirectory(m_buildDir); m_process->setProgram(m_qmakePath); m_process->setArguments(args); m_process->setProcessChannelMode(QProcess::MergedChannels); auto lineMaker = new KDevelop::ProcessLineMaker(m_process, this); connect(lineMaker, SIGNAL(receivedStdoutLines(QStringList)), m_model, SLOT(appendLines(QStringList))); connect(lineMaker, SIGNAL(receivedStderrLines(QStringList)), m_model, SLOT(appendLines(QStringList))); connect(m_process, SIGNAL(error(QProcess::ProcessError)), SLOT(processError(QProcess::ProcessError))); connect(m_process, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus))); m_process->start(); } bool QMakeJob::doKill() { if (!m_process) { return true; } m_process->kill(); return m_process->state() == m_process->NotRunning; } QString QMakeJob::errorString() const { return m_process->errorString(); } void QMakeJob::processError(QProcess::ProcessError error) { m_model->appendLine(errorString()); setError(error); emitResult(); } void QMakeJob::processFinished(int exitCode, QProcess::ExitStatus status) { if (status == QProcess::NormalExit) { m_model->appendLine(i18n("*** Exited with return code: %1 ***", exitCode)); } else if (error() == KJob::KilledJobError) { m_model->appendLine(i18n("*** Process aborted ***")); } else { m_model->appendLine(i18n("*** Crashed with return code: %1 ***", exitCode)); } emitResult(); }