Blame SOURCES/0003-Implement-automatic-scanning-of-source-code-for-requ.patch

92cace
From 89e4767148110a5566e463a03b3ed594276b7da0 Mon Sep 17 00:00:00 2001
92cace
Message-Id: <89e4767148110a5566e463a03b3ed594276b7da0.1317166378.git.kevin.kofler@chello.at>
92cace
From: Kevin Kofler <kevin.kofler@chello.at>
92cace
Date: Wed, 17 Aug 2011 04:54:37 +0200
92cace
Subject: [PATCH] Implement automatic scanning of source code for required
92cace
 data engines.
92cace
92cace
For packages in scripting languages and distributed through OCS, this is fully
92cace
automatic and triggered from Package::installPackage. If an
92cace
X-Plasma-RequiredDataEngines entry is present in the .desktop file (even if
92cace
empty), the dependency extraction is not run and the explicitly provided
92cace
information is trusted instead.
92cace
92cace
For native distribution packages, we ship a tool called
92cace
plasma-dataengine-depextractor which can be run at any time during the build
92cace
process and which adds the dependency information to the relevant .desktop file.
92cace
92cace
Authors of plasmoids are encouraged to run plasma-dataengine-depextractor and/or
92cace
fill in X-Plasma-RequiredDataEngines manually. (Please note that the list is
92cace
expected to be comma-separated.)
92cace
---
92cace
 plasma/CMakeLists.txt                 |   15 ++++
92cace
 plasma/depextractor/depextractor.cpp  |  125 +++++++++++++++++++++++++++++++++
92cace
 plasma/package.cpp                    |   11 +++
92cace
 plasma/private/componentinstaller.cpp |   71 +++++++++++++++++++
92cace
 plasma/private/componentinstaller_p.h |   17 ++++-
92cace
 5 files changed, 238 insertions(+), 1 deletions(-)
92cace
92cace
diff --git a/plasma/CMakeLists.txt b/plasma/CMakeLists.txt
92cace
index f929967..9a760ef 100644
92cace
--- a/plasma/CMakeLists.txt
92cace
+++ b/plasma/CMakeLists.txt
92cace
@@ -304,6 +304,18 @@ set_target_properties(plasma PROPERTIES
92cace
 
92cace
 install(TARGETS plasma EXPORT kdelibsLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS})
92cace
 
92cace
+if(NOT PLASMA_NO_PACKAGEKIT)
92cace
+    # we need a copy of the component installer because libplasma does not export it
92cace
+    # plus, this avoids depending on GUI stuff in this command-line utility
92cace
+    set(plasma_dataengine_depextractor_SRCS depextractor/depextractor.cpp
92cace
+                                            private/componentinstaller.cpp)
92cace
+    kde4_add_executable(plasma-dataengine-depextractor
92cace
+                        ${plasma_dataengine_depextractor_SRCS})
92cace
+    set_target_properties(plasma-dataengine-depextractor PROPERTIES
92cace
+                          COMPILE_FLAGS -DPLASMA_COMPONENTINSTALLER_NO_QWIDGET=1)
92cace
+    target_link_libraries(plasma-dataengine-depextractor ${KDE4_KDECORE_LIBS})
92cace
+endif(NOT PLASMA_NO_PACKAGEKIT)
92cace
+
92cace
 
92cace
 ########### install files ###############
92cace
 
92cace
@@ -460,3 +472,6 @@ install(FILES data/operations/dataengineservice.operations DESTINATION ${DATA_IN
92cace
 install(FILES data/operations/plasmoidservice.operations DESTINATION ${DATA_INSTALL_DIR}/plasma/services)
92cace
 install(FILES data/operations/storage.operations DESTINATION ${DATA_INSTALL_DIR}/plasma/services)
92cace
 
92cace
+if(NOT PLASMA_NO_PACKAGEKIT)
92cace
+    install(TARGETS plasma-dataengine-depextractor DESTINATION ${BIN_INSTALL_DIR})
92cace
+endif(NOT PLASMA_NO_PACKAGEKIT)
92cace
diff --git a/plasma/depextractor/depextractor.cpp b/plasma/depextractor/depextractor.cpp
92cace
new file mode 100644
92cace
index 0000000..c489de7
92cace
--- /dev/null
92cace
+++ b/plasma/depextractor/depextractor.cpp
92cace
@@ -0,0 +1,125 @@
92cace
+/* Plasma Data Engine dependency extractor
92cace
+   Copyright (C) 2011 Kevin Kofler <kevin.kofler@chello.at>
92cace
+
92cace
+   This program is free software: you can redistribute it and/or modify
92cace
+   it under the terms of the GNU General Public License as published by
92cace
+   the Free Software Foundation, either version 2 of the License, or
92cace
+   (at your option) any later version.
92cace
+
92cace
+   This program is distributed in the hope that it will be useful,
92cace
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
92cace
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
92cace
+   GNU General Public License for more details.
92cace
+
92cace
+   You should have received a copy of the GNU General Public License
92cace
+   along with this program.  If not, see <http://www.gnu.org/licenses/>. */
92cace
+
92cace
+#include <QCoreApplication>
92cace
+#include <QTextStream>
92cace
+#include <QFileInfo>
92cace
+#include <QDir>
92cace
+
92cace
+#include <cstdio>
92cace
+
92cace
+#include <kaboutdata.h>
92cace
+#include <kcmdlineargs.h>
92cace
+#include <kdesktopfile.h>
92cace
+#include <kconfiggroup.h>
92cace
+
92cace
+#include "private/componentinstaller_p.h"
92cace
+
92cace
+static QString scriptingApi(const QString &desktopFile)
92cace
+{
92cace
+    KDesktopFile desktop(desktopFile);
92cace
+    KConfigGroup desktopGroup = desktop.desktopGroup();
92cace
+    if (desktopGroup.readEntry("X-KDE-ServiceTypes", QStringList())
92cace
+                      .contains("Plasma/ScriptEngine")
92cace
+        || desktopGroup.readEntry("ServiceTypes", QStringList())
92cace
+                         .contains("Plasma/ScriptEngine")) {
92cace
+        /* Script engines are always written in C++. Their X-Plasma-API is the
92cace
+           API they export, not the language they're written in. */
92cace
+        return QString();
92cace
+    }
92cace
+    return desktopGroup.readEntry("X-Plasma-API", QString());
92cace
+}
92cace
+
92cace
+static void writeDataEngineDependencies(const QStringList &deps,
92cace
+                                        const QString &desktopFile)
92cace
+{
92cace
+    if (!deps.isEmpty()) {
92cace
+        KDesktopFile desktop(desktopFile);
92cace
+        desktop.desktopGroup().writeEntry("X-Plasma-RequiredDataEngines", deps);
92cace
+    }
92cace
+}
92cace
+
92cace
+int main(int argc, char **argv)
92cace
+{
92cace
+    KAboutData aboutData("plasma-dataengine-depextractor", QByteArray(),
92cace
+                        ki18n("Plasma Data Engine dependency extractor"),
92cace
+                        "2",
92cace
+                        ki18n("Plasma Data Engine dependency extractor"));
92cace
+    aboutData.addAuthor(ki18n("Kevin Kofler"), ki18n("Author"),
92cace
+                        "kevin.kofler@chello.at");
92cace
+
92cace
+    KCmdLineArgs::init(argc, argv, &aboutData);
92cace
+    KCmdLineOptions options;
92cace
+    options.add("+[path]",
92cace
+                ki18n("Source path (default: .)"));
92cace
+    options.add("+[file]",
92cace
+                ki18n(".desktop rel. to path (default: metadata.desktop)")
92cace
+               );
92cace
+    KCmdLineArgs::addCmdLineOptions(options);
92cace
+
92cace
+    QCoreApplication *app = new QCoreApplication(KCmdLineArgs::qtArgc(),
92cace
+                                                 KCmdLineArgs::qtArgv());
92cace
+
92cace
+    KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
92cace
+
92cace
+    int exitCode = 0;
92cace
+
92cace
+    QString path, desktopFile;
92cace
+    int argCount = args->count();
92cace
+    switch (argCount) {
92cace
+        case 0:
92cace
+            path = ".";
92cace
+            desktopFile = "metadata.desktop";
92cace
+            break;
92cace
+        case 1:
92cace
+            path = args->arg(0);
92cace
+            desktopFile = "metadata.desktop";
92cace
+            break;
92cace
+        case 2:
92cace
+            path = args->arg(0);
92cace
+            desktopFile = args->arg(1);
92cace
+            break;
92cace
+        default:
92cace
+        {
92cace
+            QTextStream err(stderr, QIODevice::WriteOnly | QIODevice::Text);
92cace
+            err << i18n("Expected at most 2 arguments, but %1 given", argCount)
92cace
+                << endl;
92cace
+            exitCode = 1;
92cace
+            break;
92cace
+        }
92cace
+    }
92cace
+
92cace
+    if (!exitCode) {
92cace
+        if (QFileInfo(desktopFile).isRelative())
92cace
+            desktopFile = QDir(path).absoluteFilePath(desktopFile);
92cace
+
92cace
+        if (QFileInfo(desktopFile).exists()) {
92cace
+            writeDataEngineDependencies(Plasma::ComponentInstaller::self()
92cace
+                                          ->extractDataEngineDependencies(
92cace
+                                              path,
92cace
+                                              scriptingApi(desktopFile)),
92cace
+                                        desktopFile);
92cace
+        } else {
92cace
+            QTextStream err(stderr, QIODevice::WriteOnly | QIODevice::Text);
92cace
+            err << i18n("Desktop file \"%1\" not found", desktopFile) << endl;
92cace
+            exitCode = 1;
92cace
+        }
92cace
+    }
92cace
+
92cace
+    args->clear();
92cace
+    delete app;
92cace
+    return exitCode;
92cace
+}
92cace
diff --git a/plasma/package.cpp b/plasma/package.cpp
92cace
index 0a45c87..131f204 100644
92cace
--- a/plasma/package.cpp
92cace
+++ b/plasma/package.cpp
92cace
@@ -631,6 +631,17 @@ bool Package::installPackage(const QString &package,
92cace
         }
92cace
     }
92cace
     QStringList requiredDataEngines = meta.requiredDataEngines();
92cace
+    if (requiredDataEngines.isEmpty()) {
92cace
+        // check whether this was explicitly specified as empty
92cace
+        QString metaPath = targetName + "/metadata.desktop";
92cace
+        KDesktopFile df(metaPath);
92cace
+        KConfigGroup cg = df.desktopGroup();
92cace
+        if (!cg.hasKey("X-Plasma-RequiredDataEngines")) {
92cace
+            // not specified at all, try running the dependency extraction
92cace
+            requiredDataEngines = ComponentInstaller::self()->extractDataEngineDependencies(targetName,
92cace
+                                                                                            requiredScriptEngine);
92cace
+        }
92cace
+    }
92cace
     if (!requiredDataEngines.isEmpty()) {
92cace
         QStringList knownDataEngines = DataEngineManager::self()->listAllEngines(meta.application());
92cace
         foreach (const QString &requiredDataEngine, requiredDataEngines) {
92cace
diff --git a/plasma/private/componentinstaller.cpp b/plasma/private/componentinstaller.cpp
92cace
index 870667f..087d1c6 100644
92cace
--- a/plasma/private/componentinstaller.cpp
92cace
+++ b/plasma/private/componentinstaller.cpp
92cace
@@ -28,6 +28,10 @@
92cace
 #include <QWidget>
92cace
 #include <QLatin1String>
92cace
 #include <QStringList>
92cace
+#include <QTextStream>
92cace
+#include <QFile>
92cace
+#include <QDirIterator>
92cace
+#include <QRegExp>
92cace
 #endif
92cace
 
92cace
 namespace Plasma
92cace
@@ -85,9 +89,13 @@ void ComponentInstaller::installMissingComponent(const QString &type,
92cace
     // We don't check packageKit.isValid() because the service is activated on
92cace
     // demand, so it will show up as "not valid".
92cace
     WId wid = 0;
92cace
+#ifndef PLASMA_COMPONENTINSTALLER_NO_QWIDGET
92cace
     if (parent) {
92cace
         wid = parent->winId();
92cace
     }
92cace
+#else
92cace
+    Q_UNUSED(parent);
92cace
+#endif
92cace
     QStringList resources;
92cace
     resources.append(searchString);
92cace
     packageKit.asyncCall(QLatin1String("InstallResources"), (unsigned int) wid,
92cace
@@ -100,4 +108,67 @@ void ComponentInstaller::installMissingComponent(const QString &type,
92cace
 #endif
92cace
 }
92cace
 
92cace
+QStringList ComponentInstaller::extractDataEngineDependencies(const QString &path,
92cace
+                                                              const QString &api)
92cace
+{
92cace
+    QStringList deps;
92cace
+
92cace
+#ifdef PLASMA_ENABLE_PACKAGEKIT_SUPPORT
92cace
+    QStringList nameFilters;
92cace
+    QRegExp searchRegExp("dataEngine *\\( *\"([^\"]+)\" *\\)");
92cace
+    if (api.isEmpty()) {
92cace
+        // no script engine API, this is native C++ code
92cace
+        nameFilters.append("*.cpp");
92cace
+        nameFilters.append("*.cxx");
92cace
+        nameFilters.append("*.cc");
92cace
+        nameFilters.append("*.C");
92cace
+        nameFilters.append("*.h");
92cace
+        nameFilters.append("*.hpp");
92cace
+        nameFilters.append("*.hxx");
92cace
+        nameFilters.append("*.hh");
92cace
+        nameFilters.append("*.H");
92cace
+    } else if (api == "declarativeappletscript") {
92cace
+        nameFilters.append("*.qml");
92cace
+        searchRegExp = QRegExp("(?:^\\s*engine:\\s*|dataEngine *\\( *)\"([^\"]+)\"");
92cace
+    } else if (api == "javascript") {
92cace
+        nameFilters.append("*.js");
92cace
+    } else if (api == "python") {
92cace
+        nameFilters.append("*.py");
92cace
+        searchRegExp = QRegExp("dataEngine *\\( *[\'\"]([^\'\"]+)[\'\"] *\\)");
92cace
+    } else if (api == "ruby-script") {
92cace
+        nameFilters.append("*.rb");
92cace
+        searchRegExp = QRegExp("dataEngine *\\( *[\'\"]([^\'\"]+)[\'\"] *\\)");
92cace
+    } else {
92cace
+        // dependency extraction not supported for this API
92cace
+        return deps;
92cace
+    }
92cace
+
92cace
+    QDirIterator it(path, nameFilters, QDir::Files | QDir::CaseSensitive,
92cace
+                    QDirIterator::Subdirectories
92cace
+                      | QDirIterator::FollowSymlinks);
92cace
+    while (it.hasNext()) {
92cace
+        QFile file(it.next());
92cace
+        file.open(QIODevice::ReadOnly | QIODevice::Text);
92cace
+        QTextStream stream(&file;;
92cace
+        QString line;
92cace
+        while (!(line = stream.readLine()).isNull()) {
92cace
+             int column = 0;
92cace
+             while ((column = searchRegExp.indexIn(line, column)) != -1) {
92cace
+                 QString dep = searchRegExp.cap(1);
92cace
+                 if (!deps.contains(dep)) {
92cace
+                     deps.append(dep);
92cace
+                 }
92cace
+                 column += searchRegExp.matchedLength();
92cace
+             }
92cace
+        }
92cace
+        file.close();
92cace
+    }
92cace
+#else
92cace
+    Q_UNUSED(path);
92cace
+    Q_UNUSED(api);
92cace
+#endif
92cace
+
92cace
+    return deps;
92cace
+}
92cace
+
92cace
 } // namespace Plasma
92cace
diff --git a/plasma/private/componentinstaller_p.h b/plasma/private/componentinstaller_p.h
92cace
index f85cbb6..d0d9c75 100644
92cace
--- a/plasma/private/componentinstaller_p.h
92cace
+++ b/plasma/private/componentinstaller_p.h
92cace
@@ -20,7 +20,7 @@
92cace
 #ifndef PLASMA_COMPONENTINSTALLER_H
92cace
 #define PLASMA_COMPONENTINSTALLER_H
92cace
 
92cace
-class QString;
92cace
+#include <QStringList>
92cace
 class QWidget;
92cace
 
92cace
 namespace Plasma
92cace
@@ -76,6 +76,21 @@ class ComponentInstaller
92cace
         void installMissingComponent(const QString &type, const QString &name,
92cace
                                      QWidget *parent = 0, bool force = false);
92cace
 
92cace
+        /**
92cace
+         * Extracts the list of required data engines from source code.
92cace
+         *
92cace
+         * If the scripting API is not supported for dependency extraction or
92cace
+         * if Plasma was compiled without support for missing component
92cace
+         * installation, an empty list of dependencies is returned.
92cace
+         *
92cace
+         * @param path the path containing the source code
92cace
+         * @param api the scripting API used;
92cace
+         *            if empty (the default), assumes the native C++ API
92cace
+         */
92cace
+        QStringList extractDataEngineDependencies(const QString &path,
92cace
+                                                  const QString &api
92cace
+                                                    = QString());
92cace
+
92cace
     private:
92cace
         /**
92cace
          * Default constructor. The singleton method self() is the
92cace
-- 
92cace
1.7.6.2
92cace