diff --git a/CMakeLists.txt b/CMakeLists.txt --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -107,6 +107,7 @@ ) install(FILES + qrc.cmake "${CMAKE_CURRENT_BINARY_DIR}/KF5PackageConfig.cmake" "${CMAKE_CURRENT_BINARY_DIR}/KF5PackageConfigVersion.cmake" "${CMAKE_CURRENT_SOURCE_DIR}/KF5PackageMacros.cmake" diff --git a/KF5PackageMacros.cmake b/KF5PackageMacros.cmake --- a/KF5PackageMacros.cmake +++ b/KF5PackageMacros.cmake @@ -21,28 +21,33 @@ # kpackage_install_package(declarativetoolbox org.kde.toolbox) # installs a generic package # +set(kpackagedir ${CMAKE_CURRENT_LIST_DIR}) function(kpackage_install_package dir component) + message(AUTHOR_WARNING "Deprecated: use kpackage_install_bundle_package") set(root ${ARGV2}) set(install_dir ${ARGV3}) if(NOT root) set(root packages) endif() if(NOT install_dir) set(install_dir ${KPACKAGE_RELATIVE_DATA_INSTALL_DIR}) endif() + install(DIRECTORY ${dir}/ DESTINATION ${KDE_INSTALL_DATADIR}/${install_dir}/${root}/${component} - PATTERN .svn EXCLUDE - PATTERN *.qmlc EXCLUDE - PATTERN CMakeLists.txt EXCLUDE - PATTERN Messages.sh EXCLUDE - PATTERN dummydata EXCLUDE) + PATTERN .svn EXCLUDE + PATTERN *.qmlc EXCLUDE + PATTERN CMakeLists.txt EXCLUDE + PATTERN Messages.sh EXCLUDE + PATTERN dummydata EXCLUDE) + set(metadatajson) if(NOT EXISTS ${component}-${root}-metadata.json AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${dir}/metadata.desktop) set(GENERATED_METADATA "${CMAKE_CURRENT_BINARY_DIR}/${component}-${root}-metadata.json") add_custom_command(OUTPUT ${GENERATED_METADATA} COMMAND KF5::desktoptojson -i ${CMAKE_CURRENT_SOURCE_DIR}/${dir}/metadata.desktop -o ${GENERATED_METADATA}) add_custom_target(${component}-${root}-metadata-json ALL DEPENDS ${GENERATED_METADATA}) install(FILES ${GENERATED_METADATA} DESTINATION ${KDE_INSTALL_DATADIR}/${install_dir}/${root}/${component} RENAME metadata.json) + set(metadatajson ${GENERATED_METADATA}) endif() @@ -77,7 +82,73 @@ set_directory_properties(PROPERTIES kpackageindex "${regenerateindex}") file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/regenerateindex.sh ${regenerateindex}) endif() + endfunction() +#use this version instead: it compresses the contents directory +#into a binary rcc file +function(kpackage_install_bundled_package dir component) + set(root ${ARGV2}) + set(install_dir ${ARGV3}) + if(NOT root) + set(root packages) + endif() + if(NOT install_dir) + set(install_dir ${KPACKAGE_RELATIVE_DATA_INSTALL_DIR}) + endif() + + set(metadatajson) + if(NOT EXISTS ${component}-${root}-metadata.json AND EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/${dir}/metadata.desktop) + set(GENERATED_METADATA "${CMAKE_CURRENT_BINARY_DIR}/${component}-${root}-metadata.json") + add_custom_command(OUTPUT ${GENERATED_METADATA} + COMMAND KF5::desktoptojson -i ${CMAKE_CURRENT_SOURCE_DIR}/${dir}/metadata.desktop -o ${GENERATED_METADATA}) + add_custom_target(${component}-${root}-metadata-json ALL DEPENDS ${GENERATED_METADATA}) + install(FILES ${GENERATED_METADATA} DESTINATION ${KDE_INSTALL_DATADIR}/${install_dir}/${root}/${component} RENAME metadata.json) + set(metadatajson ${GENERATED_METADATA}) + endif() + get_target_property(kpackagetool_cmd KF5::kpackagetool5 LOCATION) + if (${component} MATCHES "^.+\\..+\\.") #we make sure there's at least 2 dots + set(APPDATAFILE "${CMAKE_CURRENT_BINARY_DIR}/${component}.appdata.xml") + + execute_process( + COMMAND ${kpackagetool_cmd} --appstream-metainfo ${CMAKE_CURRENT_SOURCE_DIR}/${dir} --appstream-metainfo-output ${APPDATAFILE} + ERROR_VARIABLE appstreamerror + RESULT_VARIABLE result) + if (NOT result EQUAL 0) + message(WARNING "couldn't generate metainfo for ${component}: ${appstreamerror}") + else() + if(appstreamerror) + message(WARNING "warnings during generation of metainfo for ${component}: ${appstreamerror}") + endif() + + # OPTIONAL because desktop files can be NoDisplay so they render no XML. + install(FILES ${APPDATAFILE} DESTINATION ${KDE_INSTALL_METAINFODIR} OPTIONAL) + endif() + else() + message(WARNING "KPackage components should be specified in reverse domain notation. Appstream information won't be generated for ${component}.") + endif() + + set(newentry "${kpackagetool_cmd} --generate-index -g -p ${CMAKE_INSTALL_PREFIX}/${KDE_INSTALL_DATADIR}/${install_dir}/${root}\n") + get_directory_property(currentindex kpackageindex) + string(FIND "${currentindex}" "${newentry}" alreadyin) + if (alreadyin LESS 0) + set(regenerateindex "${currentindex}${newentry}") + + set_directory_properties(PROPERTIES kpackageindex "${regenerateindex}") + file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/regenerateindex.sh ${regenerateindex}) + endif() + + set(kpkgqrc "${CMAKE_CURRENT_BINARY_DIR}/${component}.qrc") + set(kpkgrcc "${CMAKE_CURRENT_BINARY_DIR}/${component}.rcc") + add_custom_command(OUTPUT ${kpkgqrc} ${kpkgrcc} + DEPENDS ${metadatajson} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/${dir} + COMMAND cmake "-Dmetadatajson=${metadatajson}" -Droot=${root} -Dinstall_dir=${install_dir} -DBINARYDIR=${CMAKE_CURRENT_BINARY_DIR} -DDIRECTORY="${CMAKE_CURRENT_SOURCE_DIR}/${dir}" -DOUTPUTFILE=${kpkgqrc} -DCOMPONENT=${component} -P ${kpackagedir}/qrc.cmake + COMMAND Qt5::rcc ${kpkgqrc} --binary -o ${kpkgrcc} + ) + add_custom_target(${component}-${root}-qrc ALL DEPENDS ${kpkgqrc}) + install(FILES ${kpkgrcc} DESTINATION ${KDE_INSTALL_DATADIR}/${install_dir}/${root}/${component}/ RENAME contents.rcc) + +endfunction() diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -22,6 +22,7 @@ fallbackpackagetest packagestructuretest plasmoidpackagetest + rccpackagetest querytest ) @@ -41,4 +42,13 @@ kpackagetool5test(${var}) endforeach() +set(kpkgqrc "${CMAKE_CURRENT_SOURCE_DIR}/data/testpackage-rcc/resources.qrc") +set(kpkgrcc "${CMAKE_CURRENT_BINARY_DIR}/testpackage-rcc/contents.rcc") +install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/data/testpackage-rcc/metadata.json" DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/testpackage-rcc) +add_custom_command(OUTPUT ${kpkgrcc} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + COMMAND Qt5::rcc ${kpkgqrc} --binary -o ${kpkgrcc}) +add_custom_target(testpackage-rcc ALL DEPENDS ${kpkgrcc}) + + add_subdirectory(mockdepresolver) diff --git a/autotests/data/testpackage-rcc/contents/images/empty.png b/autotests/data/testpackage-rcc/contents/images/empty.png new file mode 100644 diff --git a/autotests/data/testpackage-rcc/contents/ui/main.qml b/autotests/data/testpackage-rcc/contents/ui/main.qml new file mode 100644 --- /dev/null +++ b/autotests/data/testpackage-rcc/contents/ui/main.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 + +Rectangle { + id: root + color: "darkblue" +} + diff --git a/autotests/data/testpackage-rcc/contents/ui/otherfile.qml b/autotests/data/testpackage-rcc/contents/ui/otherfile.qml new file mode 100644 --- /dev/null +++ b/autotests/data/testpackage-rcc/contents/ui/otherfile.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 + +Rectangle { + id: root + color: "darkblue" +} + diff --git a/autotests/data/testpackage-rcc/metadata.json b/autotests/data/testpackage-rcc/metadata.json new file mode 100644 --- /dev/null +++ b/autotests/data/testpackage-rcc/metadata.json @@ -0,0 +1,24 @@ +{ + "KPlugin": { + "Authors": [ + { + "Email": "jblow@kde.org", + "Name": "Joe Blow" + } + ], + "Category": "", + "Description": "fancy shmancy summary", + "Icon": "plasma", + "Id": "org.kde.testpackagercc", + "License": "GPLv2+", + "Name": "Test QResource Package", + "ServiceTypes": [ + "KPackage/Generic" + ], + "Version": "", + "Website": "" + }, + "Keywords": "", + "X-KDE-ParentApp": "", + "X-Plasma-MainScript": "ui/main.qml" +} diff --git a/autotests/data/testpackage-rcc/resources.qrc b/autotests/data/testpackage-rcc/resources.qrc new file mode 100644 --- /dev/null +++ b/autotests/data/testpackage-rcc/resources.qrc @@ -0,0 +1,8 @@ + + + ./metadata.json + ./contents/ui/main.qml + ./contents/ui/otherfile.qml + ./contents/images/empty.png + + diff --git a/autotests/rccpackagetest.h b/autotests/rccpackagetest.h new file mode 100644 --- /dev/null +++ b/autotests/rccpackagetest.h @@ -0,0 +1,47 @@ +/****************************************************************************** +* Copyright 2007 by Aaron Seigo * +* Copyright 2014 Marco Martin * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Library General Public * +* License as published by the Free Software Foundation; either * +* version 2 of the License, or (at your option) any later version. * +* * +* This library is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +* Library General Public License for more details. * +* * +* You should have received a copy of the GNU Library General Public License * +* along with this library; see the file COPYING.LIB. If not, write to * +* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * +* Boston, MA 02110-1301, USA. * +*******************************************************************************/ + +#ifndef RCCPACKAGETEST_H +#define RCCPACKAGETEST_H + +#include + +#include "kpackage/package.h" + +class RccPackageTest : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void initTestCase(); + void cleanupTestCase(); + void accessFiles(); + void validate(); + void testResourceRefCount(); + + +private: + KPackage::Package *m_pkg; + KPackage::Package *m_pkg2; + QString m_packagePath; +}; + +#endif + diff --git a/autotests/rccpackagetest.cpp b/autotests/rccpackagetest.cpp new file mode 100644 --- /dev/null +++ b/autotests/rccpackagetest.cpp @@ -0,0 +1,105 @@ +/****************************************************************************** +* Copyright 2007 by Aaron Seigo * +* Copyright 2014 Marco Martin * +* * +* 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 "rccpackagetest.h" + + +#include + +#include +#include "packagestructure.h" +#include "plasmoidstructure.h" +#include "packageloader.h" + +void RccPackageTest::initTestCase() +{ + QStandardPaths::enableTestMode(true); + + QString destination = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/plasma/plasmoids/testpackage-rcc/"); + QDir dir; + dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)); + dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/plasma/")); + dir.mkpath(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + QStringLiteral("/plasma/plasmoids/")); + dir.mkpath(destination); + QVERIFY(dir.exists(destination)); + + //verify the source files exist + QVERIFY(QFile::exists(QStringLiteral("./autotests/testpackage-rcc/metadata.json"))); + QVERIFY(QFile::exists(QStringLiteral("./autotests/testpackage-rcc/contents.rcc"))); + + QFile::copy(QStringLiteral("./autotests/testpackage-rcc/metadata.json"), destination + QStringLiteral("metadata.json")); + QFile::copy(QStringLiteral("./autotests/testpackage-rcc/contents.rcc"), destination + QStringLiteral("contents.rcc")); + + //verify they have been copied correctly + QVERIFY(QFile::exists(destination + QStringLiteral("metadata.json"))); + QVERIFY(QFile::exists(destination + QStringLiteral("contents.rcc"))); + + m_packagePath = "testpackage-rcc"; + m_pkg = new KPackage::Package(new KPackage::PlasmoidPackage); + m_pkg->setPath(m_packagePath); + //Two package instances with the same resource + m_pkg2 = new KPackage::Package(new KPackage::PlasmoidPackage); + m_pkg2->setPath(m_packagePath); +} + +void RccPackageTest::cleanupTestCase() +{ + qDebug() << "cleaning up"; + // Clean things up. + QDir(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation)).removeRecursively(); +} + +void RccPackageTest::accessFiles() +{ + QVERIFY(m_pkg->hasValidStructure()); + QVERIFY(m_pkg->hasValidStructure()); + + //check for existence of all files + QVERIFY(!m_pkg->filePath("ui", QStringLiteral("main.qml")).isEmpty()); + QVERIFY(!m_pkg->filePath("ui", QStringLiteral("otherfile.qml")).isEmpty()); + QVERIFY(!m_pkg->filePath("images", QStringLiteral("empty.png")).isEmpty()); + + QVERIFY(!m_pkg2->filePath("ui", QStringLiteral("main.qml")).isEmpty()); + QVERIFY(!m_pkg2->filePath("ui", QStringLiteral("otherfile.qml")).isEmpty()); + QVERIFY(!m_pkg2->filePath("images", QStringLiteral("empty.png")).isEmpty()); +} + +void RccPackageTest::validate() +{ + QCOMPARE(m_pkg->cryptographicHash(QCryptographicHash::Sha1), QStringLiteral("ed0959c062b7ef7eaab5243e83e2f9afe5b3b15f")); + QCOMPARE(m_pkg2->cryptographicHash(QCryptographicHash::Sha1), QStringLiteral("ed0959c062b7ef7eaab5243e83e2f9afe5b3b15f")); +} + +void RccPackageTest::testResourceRefCount() +{ + delete m_pkg; + m_pkg = nullptr; + + QVERIFY(m_pkg2->isValid()); + QVERIFY(QFile::exists(QStringLiteral(":/plasma/plasmoids/testpackage-rcc/contents/ui/main.qml"))); + + //no reference from this package anymore + delete m_pkg2; + m_pkg2 = nullptr; + QVERIFY(!QFile::exists(QStringLiteral(":/plasma/plasmoids/testpackage-rcc/contents/ui/main.qml"))); +} + +QTEST_MAIN(RccPackageTest) + diff --git a/qrc.cmake b/qrc.cmake new file mode 100644 --- /dev/null +++ b/qrc.cmake @@ -0,0 +1,17 @@ +set(OUTPUT "\n + +") + +file(GLOB_RECURSE files LIST_DIRECTORIES FALSE RELATIVE ${DIRECTORY} ${DIRECTORY}/*) +foreach(v IN LISTS files) + set(OUTPUT "${OUTPUT} ${DIRECTORY}/${v}\n") +endforeach() + +if (metadatajson) + set(OUTPUT "${OUTPUT} ${metadatajson}\n") +endif() + +set(OUTPUT "${OUTPUT} + +") +file(WRITE "${OUTPUTFILE}" "${OUTPUT}") diff --git a/src/kpackage/package.h b/src/kpackage/package.h --- a/src/kpackage/package.h +++ b/src/kpackage/package.h @@ -23,6 +23,7 @@ #include #include #include +#include #include diff --git a/src/kpackage/package.cpp b/src/kpackage/package.cpp --- a/src/kpackage/package.cpp +++ b/src/kpackage/package.cpp @@ -23,6 +23,7 @@ #include "package.h" #include +#include #include #include "kpackage_debug.h" @@ -213,6 +214,7 @@ //qCDebug(KPACKAGE_LOG) << "metadata: " << d->path << filePath("metadata"); if (!d->metadata && !d->path.isEmpty()) { const QString metadataPath = filePath("metadata"); + if (!metadataPath.isEmpty()) { d->createPackageMetadata(metadataPath); } else { @@ -401,7 +403,13 @@ QUrl Package::fileUrl(const QByteArray &fileType, const QString &filename) const { - return QUrl::fromLocalFile(filePath(fileType, filename)); + QString path = filePath(fileType, filename); + //construct a qrc:/ url or a file:/ url, the only two protocols supported + if (path.startsWith(QStringLiteral(":"))) { + return QUrl(QStringLiteral("qrc") + path); + } else { + return QUrl::fromLocalFile(path); + } } QStringList Package::entryList(const QByteArray &key) const @@ -504,6 +512,7 @@ } if (QDir::isRelativePath(p)) { + //FIXME: can searching of the qrc be better? paths << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, p, QStandardPaths::LocateDirectory); } else { const QDir dir(p); @@ -532,7 +541,16 @@ foreach (const QString &p, paths) { d->checkedValid = false; QDir dir(p); + Q_ASSERT(QFile::exists(dir.canonicalPath())); + + //if it has a contents.rcc, give priority to it + if (dir.exists(QStringLiteral("contents.rcc"))) { + d->rccPath = dir.absoluteFilePath(QStringLiteral("contents.rcc")); + QResource::registerResource(d->rccPath); + dir = QDir(QStringLiteral(":/") + defaultPackageRoot() + path); + } + d->path = dir.canonicalPath(); // canonicalPath() does not include a trailing slash (unless it is the root dir) if (!d->path.endsWith(QLatin1Char('/'))) { @@ -861,6 +879,13 @@ PackagePrivate::~PackagePrivate() { + if (!rccPath.isEmpty()) { + //qresource register/unregisterpath is refcounted if we call it two times + //on the same path, the resource will actually be unregistered only when + //unregister is called 2 times + QResource::unregisterResource(rccPath); + } + if (!tempRoot.isEmpty()) { QDir dir(tempRoot); dir.removeRecursively(); diff --git a/src/kpackage/private/package_p.h b/src/kpackage/private/package_p.h --- a/src/kpackage/private/package_p.h +++ b/src/kpackage/private/package_p.h @@ -101,6 +101,7 @@ Package *fallbackPackage; QStringList mimeTypes; KPluginMetaData *metadata; + QString rccPath; bool externalPaths : 1; bool valid : 1; bool checkedValid : 1;