diff options
author | Robert Griebl <robert.griebl@pelagicore.com> | 2017-05-23 11:21:27 +0200 |
---|---|---|
committer | Dominik Holland <dominik.holland@pelagicore.com> | 2017-06-02 12:57:19 +0000 |
commit | 0fa1e466ee0043fccc76f5b71e1c4b80c28f1107 (patch) | |
tree | c1568f0192240d056d4c1b00f410454a809177b6 | |
parent | 61d0099d3e46229963cfac348720817494cad37f (diff) |
Refactor the src/manager dir into a sub-module
* the appman binary is now built from src/tools/appman
* renamed all the main.cpp files to the names of their containing directories
(avoids a lot of confusion)
* all dependencies between the src modules and the tools are now handled
centrally in src.pro
* removed the Android deployment, since it doesn't work as-is on newer
Android version anymore - we have to come up with a new plan on how to
package up the AM plus a system-ui in one APK
Change-Id: Ieb4536a7ab2a301488de09db61844da092620859
Reviewed-by: Dominik Holland <dominik.holland@pelagicore.com>
38 files changed, 858 insertions, 1009 deletions
diff --git a/src/launchers/launchers.pro b/src/launchers/launchers.pro deleted file mode 100644 index c9b4c9e6..00000000 --- a/src/launchers/launchers.pro +++ /dev/null @@ -1,5 +0,0 @@ -TEMPLATE = subdirs - -load(am-config) - -multi-process:qtHaveModule(dbus):SUBDIRS = qml diff --git a/src/manager/.gitignore b/src/main-lib/.gitignore index 71cd379f..71cd379f 100644 --- a/src/manager/.gitignore +++ b/src/main-lib/.gitignore diff --git a/src/manager/configuration.cpp b/src/main-lib/configuration.cpp index 0507d979..ba38e194 100644 --- a/src/manager/configuration.cpp +++ b/src/main-lib/configuration.cpp @@ -71,25 +71,34 @@ QT_BEGIN_NAMESPACE_AM template<> bool Configuration::value(const char *clname, const QVector<const char *> &cfname) const { - return m_clp.isSet(qL1S(clname)) || findInConfigFile(cfname).toBool(); + return (clname && m_clp.isSet(qL1S(clname))) || findInConfigFile(cfname).toBool(); } template<> QString Configuration::value(const char *clname, const QVector<const char *> &cfname) const { - QString clval = m_clp.value(qL1S(clname)); + QString clval; + if (clname) + clval = m_clp.value(qL1S(clname)); bool cffound; QString cfval = findInConfigFile(cfname, &cffound).toString(); - return (m_clp.isSet(qL1S(clname)) || !cffound) ? clval : cfval; + return ((clname && m_clp.isSet(qL1S(clname))) || !cffound) ? clval : cfval; } template<> QStringList Configuration::value(const char *clname, const QVector<const char *> &cfname) const { - return m_clp.values(qL1S(clname)) + variantToStringList(findInConfigFile(cfname)); + QStringList result; + if (clname) + result = m_clp.values(qL1S(clname)); + if (!cfname.isEmpty()) + result += variantToStringList(findInConfigFile(cfname)); + return result; } template<> QVariant Configuration::value(const char *clname, const QVector<const char *> &cfname) const { - QString yaml = m_clp.value(qL1S(clname)); + QString yaml; + if (clname) + yaml = m_clp.value(qL1S(clname)); if (!yaml.isEmpty()) { auto docs = QtYaml::variantDocumentsFromYaml(yaml.toUtf8()); return docs.isEmpty() ? QVariant() : docs.constFirst(); @@ -158,6 +167,15 @@ Configuration::Configuration(const QString &defaultConfigFilePath, const QString m_clp.addOption({ qSL("build-config"), qSL("dumps the build configuration and exits.") }); } +QVariant Configuration::buildConfig() const +{ + QFile f(m_buildConfigFilePath); + if (f.open(QFile::ReadOnly)) + return QtYaml::variantDocumentsFromYaml(f.readAll()).toList(); + else + return QVariant(); +} + Configuration::~Configuration() { diff --git a/src/manager/configuration.h b/src/main-lib/configuration.h index af937051..3cc633b6 100644 --- a/src/manager/configuration.h +++ b/src/main-lib/configuration.h @@ -54,6 +54,7 @@ class Configuration public: virtual ~Configuration(); virtual void parse(); + QVariant buildConfig() const; protected: Configuration(const QString &defaultConfigFilePath, const QString &buildConfigFilePath); diff --git a/src/manager/defaultconfiguration.cpp b/src/main-lib/defaultconfiguration.cpp index 0beac426..cab61f0a 100644 --- a/src/manager/defaultconfiguration.cpp +++ b/src/main-lib/defaultconfiguration.cpp @@ -53,33 +53,28 @@ QT_BEGIN_NAMESPACE_AM -DefaultConfiguration::DefaultConfiguration() +DefaultConfiguration::DefaultConfiguration(const char *additionalDescription, bool onlyOnePositionalArgument) : Configuration(qSL(AM_CONFIG_FILE), qSL(":/build-config.yaml")) + , m_onlyOnePositionalArgument(onlyOnePositionalArgument) { // using QStringLiteral for all strings here adds a few KB of ro-data, but will also improve // startup times slightly: less allocations and copies. MSVC cannot cope with multi-line though const char *description = -#ifdef AM_TESTRUNNER - "Pelagicore ApplicationManager QML Test Runner" - "\n\n" - "Additional testrunner commandline options can be set after the -- argument\n" - "Use -- -help to show all available testrunner commandline options" -#else - "Pelagicore ApplicationManager" -#endif - "\n\n" "In addition to the commandline options below, the following environment\n" "variables can be set:\n\n" " AM_STARTUP_TIMER if set to 1, a startup performance analysis will be printed\n" " on the console. Anything other than 1 will be interpreted\n" - " as the name of a file that is used instead of the console\n" + " as the name of a file that is used instead of the console.\n" "\n" " AM_FORCE_COLOR_OUTPUT can be set to 'on' to force color output to the console\n" " and to 'off' to disable it. Any other value will result\n" " in the default, auto-detection behavior.\n"; - m_clp.setApplicationDescription(description); + m_clp.setApplicationDescription(QCoreApplication::organizationName() + qL1C(' ') + + QCoreApplication::applicationName() + qSL("\n\n") + + (additionalDescription ? (qL1S(additionalDescription) + qSL("\n\n")) : QString()) + + qL1S(description)); m_clp.addPositionalArgument(qSL("qml-file"), qSL("the main QML file.")); m_clp.addOption({ qSL("database"), qSL("application database."), qSL("file"), qSL("/opt/am/apps.db") }); @@ -122,12 +117,10 @@ void DefaultConfiguration::parse() { Configuration::parse(); -#ifndef AM_TESTRUNNER - if (m_clp.positionalArguments().size() > 1) { + if (m_onlyOnePositionalArgument && (m_clp.positionalArguments().size() > 1)) { showParserMessage(qL1S("Only one main qml file can be specified.\n"), ErrorMessage); exit(1); } -#endif } QString DefaultConfiguration::mainQmlFile() const @@ -146,7 +139,7 @@ QString DefaultConfiguration::database() const bool DefaultConfiguration::recreateDatabase() const { - return m_clp.isSet("recreate-database"); + return value<bool>("recreate-database"); } QStringList DefaultConfiguration::builtinAppsManifestDirs() const @@ -171,7 +164,7 @@ bool DefaultConfiguration::fullscreen() const bool DefaultConfiguration::noFullscreen() const { - return m_clp.isSet("no-fullscreen"); + return value<bool>("no-fullscreen"); } QString DefaultConfiguration::windowIcon() const @@ -298,16 +291,14 @@ QVariantMap DefaultConfiguration::runtimeConfigurations() const return value<QVariant>(nullptr, { "runtimes" }).toMap(); } -QVariantMap DefaultConfiguration::dbusPolicy(const QString &interfaceName) const +QVariantMap DefaultConfiguration::dbusPolicy(const char *interfaceName) const { - QByteArray name = interfaceName.toLocal8Bit(); - return value<QVariant>(nullptr, { "dbus", name.constData(), "policy" }).toMap(); + return value<QVariant>(nullptr, { "dbus", interfaceName, "policy" }).toMap(); } -QString DefaultConfiguration::dbusRegistration(const QString &interfaceName) const +QString DefaultConfiguration::dbusRegistration(const char *interfaceName) const { - QByteArray name = interfaceName.toLocal8Bit(); - QString dbus = value<QString>("dbus", { "dbus", name.constData(), "register" }); + QString dbus = value<QString>("dbus", { "dbus", interfaceName, "register" }); if (dbus == qL1S("none")) dbus.clear(); return dbus; diff --git a/src/manager/defaultconfiguration.h b/src/main-lib/defaultconfiguration.h index ed210837..66ec46ef 100644 --- a/src/manager/defaultconfiguration.h +++ b/src/main-lib/defaultconfiguration.h @@ -42,14 +42,14 @@ #pragma once #include <QtAppManCommon/global.h> -#include "configuration.h" +#include <QtAppManMain/configuration.h> QT_BEGIN_NAMESPACE_AM class DefaultConfiguration : public Configuration { public: - DefaultConfiguration(); + DefaultConfiguration(const char *additionalDescription, bool onlyOnePositionalArgument); ~DefaultConfiguration(); void parse(); @@ -85,8 +85,8 @@ public: QVariantMap containerConfigurations() const; QVariantMap runtimeConfigurations() const; - QVariantMap dbusPolicy(const QString &interfaceName) const; - QString dbusRegistration(const QString &interfaceName) const; + QVariantMap dbusPolicy(const char *interfaceName) const; + QString dbusRegistration(const char *interfaceName) const; int dbusRegistrationDelay() const; bool dbusStartSessionBus() const; @@ -114,6 +114,7 @@ public: private: QString m_mainQmlFile; + bool m_onlyOnePositionalArgument = false; }; QT_END_NAMESPACE_AM diff --git a/src/manager/manager.pri b/src/main-lib/main-lib.pro index b3514fb0..824f8ce1 100644 --- a/src/manager/manager.pri +++ b/src/main-lib/main-lib.pro @@ -1,3 +1,7 @@ +TEMPLATE = lib +TARGET = QtAppManMain +MODULE = appman_main + load(am-config) QT = core network qml core-private @@ -15,7 +19,7 @@ QT *= \ appman_window-private \ appman_monitor-private \ -CONFIG *= console +CONFIG *= static internal_module win32:LIBS += -luser32 @@ -50,17 +54,4 @@ dbus-appman.header_flags = -l QtAM::ApplicationManager -i applicationmanager.h DBUS_ADAPTORS += dbus-notifications dbus-appman -load(qt_tool) - -load(install-prefix) - -OTHER_FILES = \ - syms.txt \ - -load(build-config) - -unix:exists($$SOURCE_DIR/.git):GIT_VERSION=$$system(cd "$$SOURCE_DIR" && git describe --tags --always --dirty 2>/dev/null) -isEmpty(GIT_VERSION):GIT_VERSION="unknown" - -createBuildConfig(_DATE_, VERSION, GIT_VERSION, SOURCE_DIR, BUILD_DIR, INSTALL_PREFIX, \ - QT_ARCH, QT_VERSION, QT, CONFIG, DEFINES, INCLUDEPATH, LIBS) +load(qt_module) diff --git a/src/manager/main.cpp b/src/main-lib/main.cpp index 852d48bb..85a6dfad 100644 --- a/src/manager/main.cpp +++ b/src/main-lib/main.cpp @@ -135,69 +135,6 @@ #include "../plugin-interfaces/startupinterface.h" -#if defined(AM_TESTRUNNER) -# include "testrunner.h" -# include "qtyaml.h" -#endif - - -QT_USE_NAMESPACE_AM - -Q_DECL_EXPORT int main(int argc, char *argv[]) -{ -#if defined(Q_OS_UNIX) && defined(AM_MULTI_PROCESS) - // set a reasonable default for OSes/distros that do not set this by default - setenv("XDG_RUNTIME_DIR", "/tmp", 0); -#endif - - StartupTimer::instance()->checkpoint("entered main"); - - QCoreApplication::setApplicationName(qSL("ApplicationManager")); - QCoreApplication::setOrganizationName(qSL("Pelagicore AG")); - QCoreApplication::setOrganizationDomain(qSL("pelagicore.com")); - QCoreApplication::setApplicationVersion(qSL(AM_VERSION)); - for (int i = 1; i < argc; ++i) { - if (strcmp("--no-dlt-logging", argv[i]) == 0) { - Logging::setDltEnabled(false); - break; - } - } - Logging::initialize(); - StartupTimer::instance()->checkpoint("after basic initialization"); - -#if !defined(AM_DISABLE_INSTALLER) - Package::ensureCorrectLocale(); - - QString error; - if (Q_UNLIKELY(!forkSudoServer(DropPrivilegesPermanently, &error))) { - qCCritical(LogSystem) << "ERROR:" << qPrintable(error); - return 2; - } - StartupTimer::instance()->checkpoint("after sudo server fork"); -#endif - - DefaultConfiguration cfg; - cfg.parse(); - StartupTimer::instance()->checkpoint("after command line parse"); - - try { -#if !defined(AM_HEADLESS) - // this is needed for both WebEngine and Wayland Multi-screen rendering - QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); -# if !defined(QT_NO_SESSIONMANAGER) - QGuiApplication::setFallbackSessionManagementEnabled(false); -# endif -#endif - Main a(argc, argv); - - a.setup(&cfg); - return a.exec(); - - } catch (const std::exception &e) { - qCCritical(LogSystem) << "ERROR:" << e.what(); - return 2; - } -} QT_BEGIN_NAMESPACE_AM @@ -215,13 +152,23 @@ Main::Main(int &argc, char **argv) Main::~Main() { -#if defined(AM_TESTRUNNER) - Q_UNUSED(m_notificationManager); - Q_UNUSED(m_systemMonitor); - Q_UNUSED(m_applicationIPCManager); - Q_UNUSED(m_debuggingEnabler); - delete m_engine; -#else + // the eventloop stopped, so any pending "retakes" would not be executed + QObject *singletons[] = { + m_applicationManager, + m_applicationInstaller, + m_applicationIPCManager, + m_notificationManager, + m_windowManager, + m_systemMonitor + }; + for (const auto &singleton : singletons) + retakeSingletonOwnershipFromQmlEngine(m_engine, singleton, true); + +#if defined(QT_PSSDP_LIB) + if (m_ssdpOk) + m_ssdp.setActive(false); +#endif // QT_PSSDP_LIB + delete m_engine; delete m_notificationManager; @@ -234,7 +181,6 @@ Main::~Main() delete m_systemMonitor; delete m_applicationIPCManager; delete m_debuggingEnabler; -#endif // defined(AM_TESTRUNNER) } /*! \internal @@ -253,10 +199,6 @@ void Main::setup(const DefaultConfiguration *cfg) Q_DECL_NOEXCEPT_EXPR(false) setupQmlDebugging(cfg->qmlDebugging()); Logging::registerUnregisteredDltContexts(); -#if defined(AM_TESTRUNNER) - TestRunner::initialize(cfg->testRunnerArguments()); -#endif - loadStartupPlugins(cfg->pluginFilePaths("startup")); parseSystemProperties(cfg->rawSystemProperties()); @@ -289,35 +231,6 @@ void Main::setup(const DefaultConfiguration *cfg) Q_DECL_NOEXCEPT_EXPR(false) setupSSDPService(); } -int Main::exec() Q_DECL_NOEXCEPT_EXPR(false) -{ - int res; -#if defined(AM_TESTRUNNER) - res = TestRunner::exec(m_engine); -#else - res = MainBase::exec(); - - // the eventloop stopped, so any pending "retakes" would not be executed - QObject *singletons[] = { - m_applicationManager, - m_applicationInstaller, - m_applicationIPCManager, - m_notificationManager, - m_windowManager, - m_systemMonitor - }; - for (const auto &singleton : singletons) - retakeSingletonOwnershipFromQmlEngine(m_engine, singleton, true); -#endif // defined(AM_TESTRUNNER) - -#if defined(QT_PSSDP_LIB) - if (m_ssdpOk) - m_ssdp.setActive(false); -#endif // QT_PSSDP_LIB - - return res; -} - bool Main::isSingleProcessMode() const { return m_isSingleProcessMode; @@ -358,6 +271,11 @@ void Main::shutDown() } } +QQmlApplicationEngine *Main::qmlEngine() const +{ + return m_engine; +} + void Main::setupQmlDebugging(bool qmlDebugging) { if (qmlDebugging) { @@ -605,6 +523,11 @@ void Main::loadApplicationDatabase(const QString &databasePath, bool recreateDat } if (!m_applicationDatabase->isValid() || recreateDatabase) { + const QString dbDir = QFileInfo(databasePath).absolutePath(); + + if (Q_UNLIKELY(!QDir(dbDir).exists()) && Q_UNLIKELY(!QDir::root().mkpath(dbDir))) + throw Exception("could not create application database directory %1").arg(dbDir); + QVector<const Application *> apps; if (!singleApp.isEmpty()) { @@ -678,18 +601,9 @@ void Main::setupQmlEngine(const QStringList &importPaths, const QString &quickCo new QmlLogger(m_engine); m_engine->setOutputWarningsToStandardError(false); m_engine->setImportPathList(m_engine->importPathList() + importPaths); - m_engine->rootContext()->setContextProperty("StartupTimer", StartupTimer::instance()); + m_engine->rootContext()->setContextProperty(qSL("StartupTimer"), StartupTimer::instance()); StartupTimer::instance()->checkpoint("after QML engine instantiation"); - -#if defined(AM_TESTRUNNER) - QFile f(qSL(":/build-config.yaml")); - QVector<QVariant> docs; - if (f.open(QFile::ReadOnly)) - docs = QtYaml::variantDocumentsFromYaml(f.readAll()); - f.close(); - m_engine->rootContext()->setContextProperty("buildConfig", docs.toList()); -#endif } void Main::setupWindowTitle(const QString &title, const QString &iconPath) @@ -887,7 +801,7 @@ const char *Main::dbusInterfaceName(QObject *o) const Q_DECL_NOEXCEPT_EXPR(false int idx = o->metaObject()->indexOfClassInfo("D-Bus Interface"); if (idx < 0) { throw Exception("Could not get class-info \"D-Bus Interface\" for D-Bus adapter %1") - .arg(o->metaObject()->className()); + .arg(qL1S(o->metaObject()->className())); } return o->metaObject()->classInfo(idx).value(); #else @@ -905,14 +819,14 @@ void Main::registerDBusObject(QDBusAbstractAdaptor *adaptor, const QString &dbus if (dbusName.isEmpty()) { return; } else if (dbusName == qL1S("system")) { - dbusAddress = qgetenv("DBUS_SYSTEM_BUS_ADDRESS"); + dbusAddress = QString::fromLocal8Bit(qgetenv("DBUS_SYSTEM_BUS_ADDRESS")); # if defined(Q_OS_LINUX) if (dbusAddress.isEmpty()) dbusAddress = qL1S("unix:path=/var/run/dbus/system_bus_socket"); # endif conn = QDBusConnection::systemBus(); } else if (dbusName == qL1S("session")) { - dbusAddress = qgetenv("DBUS_SESSION_BUS_ADDRESS"); + dbusAddress = QString::fromLocal8Bit(qgetenv("DBUS_SESSION_BUS_ADDRESS")); conn = QDBusConnection::sessionBus(); } else { dbusAddress = dbusName; @@ -932,12 +846,12 @@ void Main::registerDBusObject(QDBusAbstractAdaptor *adaptor, const QString &dbus if (!conn.registerObject(qL1S(path), adaptor->parent(), QDBusConnection::ExportAdaptors)) { throw Exception("could not register object %1 on D-Bus (%2): %3") - .arg(path).arg(dbusName).arg(conn.lastError().message()); + .arg(qL1S(path)).arg(dbusName).arg(conn.lastError().message()); } if (!conn.registerService(qL1S(serviceName))) { throw Exception("could not register service %1 on D-Bus (%2): %3") - .arg(serviceName).arg(dbusName).arg(conn.lastError().message()); + .arg(qL1S(serviceName)).arg(dbusName).arg(conn.lastError().message()); } qCDebug(LogSystem).nospace().noquote() << " * " << serviceName << path << " [on bus: " << dbusName << "]"; @@ -949,7 +863,7 @@ void Main::registerDBusObject(QDBusAbstractAdaptor *adaptor, const QString &dbus QFile f(QDir::temp().absoluteFilePath(qL1S(interfaceName) + qSL(".dbus"))); QByteArray dbusUtf8 = dbusAddress.isEmpty() ? dbusName.toUtf8() : dbusAddress.toUtf8(); if (!f.open(QFile::WriteOnly | QFile::Truncate) || (f.write(dbusUtf8) != dbusUtf8.size())) - throw Exception(f, "Could not write D-Bus address of interface %1").arg(interfaceName); + throw Exception(f, "Could not write D-Bus address of interface %1").arg(qL1S(interfaceName)); static QStringList filesToDelete; if (filesToDelete.isEmpty()) @@ -963,8 +877,8 @@ void Main::registerDBusObject(QDBusAbstractAdaptor *adaptor, const QString &dbus #endif // QT_DBUS_LIB } -void Main::registerDBusInterfaces(const std::function<QString(const QString &)> &busForInterface, - const std::function<QVariantMap(const QString &)> &policyForInterface) +void Main::registerDBusInterfaces(const std::function<QString(const char *)> &busForInterface, + const std::function<QVariantMap(const char *)> &policyForInterface) { #if defined(QT_DBUS_LIB) registerDBusTypes(); diff --git a/src/manager/main.h b/src/main-lib/main.h index d137cdb5..a9f3df4d 100644 --- a/src/manager/main.h +++ b/src/main-lib/main.h @@ -88,18 +88,19 @@ public: bool isSingleProcessMode() const; void setup(const DefaultConfiguration *cfg) Q_DECL_NOEXCEPT_EXPR(false); - int exec() Q_DECL_NOEXCEPT_EXPR(false); void shutDown(); + QQmlApplicationEngine *qmlEngine() const; + protected: void setupQmlDebugging(bool qmlDebugging); void setupLoggingRules(bool verbose, const QStringList &loggingRules); void loadStartupPlugins(const QStringList &startupPluginPaths) Q_DECL_NOEXCEPT_EXPR(false); void parseSystemProperties(const QVariantMap &rawSystemProperties); void setupDBus(bool startSessionBus) Q_DECL_NOEXCEPT_EXPR(false); - void registerDBusInterfaces(const std::function<QString(const QString &)> &busForInterface, - const std::function<QVariantMap(const QString &)> &policyForInterface); + void registerDBusInterfaces(const std::function<QString(const char *)> &busForInterface, + const std::function<QVariantMap(const char *)> &policyForInterface); void setMainQmlFile(const QString &mainQml) Q_DECL_NOEXCEPT_EXPR(false); void setupSingleOrMultiProcess(bool forceSingleProcess, bool forceMultiProcess) Q_DECL_NOEXCEPT_EXPR(false); void setupRuntimesAndContainers(const QVariantMap &runtimeConfigurations, const QVariantMap &containerConfigurations, diff --git a/src/manager/qmllogger.cpp b/src/main-lib/qmllogger.cpp index 21889251..21889251 100644 --- a/src/manager/qmllogger.cpp +++ b/src/main-lib/qmllogger.cpp diff --git a/src/manager/qmllogger.h b/src/main-lib/qmllogger.h index 7ea18cb5..7ea18cb5 100644 --- a/src/manager/qmllogger.h +++ b/src/main-lib/qmllogger.h diff --git a/src/manager/android-deploy-dummy.qml b/src/manager/android-deploy-dummy.qml deleted file mode 100644 index be606e97..00000000 --- a/src/manager/android-deploy-dummy.qml +++ /dev/null @@ -1,60 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Pelagicore AG -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Pelagicore Application Manager. -** -** $QT_BEGIN_LICENSE:LGPL-QTAS$ -** Commercial License Usage -** Licensees holding valid commercial Qt Automotive Suite licenses may use -** this file in accordance with the commercial license agreement provided -** with the Software or, alternatively, in accordance with the terms -** contained in a written agreement between you and The Qt Company. For -** licensing terms and conditions see https://www.qt.io/terms-conditions. -** For further information use the contact form at https://www.qt.io/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 3 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL3 included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 3 requirements -** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 2.0 or (at your option) the GNU General -** Public license version 3 or any later version approved by the KDE Free -** Qt Foundation. The licenses are as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-2.0.html and -** https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -** SPDX-License-Identifier: LGPL-3.0 -** -****************************************************************************/ - -import QtGraphicalEffects 1.0 -import Qt.labs.folderlistmodel 2.0 -import QtMultimedia 5.0 -import QtQuick 2.1 -import QtQuick.Controls 1.1 -import QtQuick.Controls.Styles 1.0 -import QtQuick.Layouts 1.1 -import QtQuick.Window 2.1 -import QtWebKit 3.0 -import QtQuick.LocalStorage 2.0 -import com.pelagicore.datasource 0.1 -import com.pelagicore.ScreenManager 0.1 -import com.theqtcompany.comtqci18ndemo 0.1 - -Rectangle { - width: 100 - height: 62 -} - diff --git a/src/manager/android/AndroidManifest.xml b/src/manager/android/AndroidManifest.xml deleted file mode 100644 index 94356084..00000000 --- a/src/manager/android/AndroidManifest.xml +++ /dev/null @@ -1,80 +0,0 @@ -<?xml version="1.0"?> -<manifest package="io.qt.ApplicationManager" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.1" android:versionCode="1" android:installLocation="auto"> - <application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="Qt ApplicationManager" android:icon="@drawable/icon" android:theme="@android:style/Theme.Holo.NoActionBar"> - <activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="Qt ApplicationManager" android:screenOrientation="unspecified" android:launchMode="singleTop"> - <intent-filter> - <action android:name="android.intent.action.MAIN"/> - <category android:name="android.intent.category.LAUNCHER"/> - </intent-filter> - - <!-- Application arguments --> - <!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ --> - <!-- Application arguments --> - - <meta-data android:name="android.app.lib_name" android:value="appman"/> - <meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/> - <meta-data android:name="android.app.repository" android:value="default"/> - <meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/> - <meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/> - <!-- Deploy Qt libs as part of package --> - <meta-data android:name="android.app.bundle_local_qt_libs" android:value="-- %%BUNDLE_LOCAL_QT_LIBS%% --"/> - <meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/> - <meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/> - <!-- Run with local libs --> - <meta-data android:name="android.app.use_local_qt_libs" android:value="-- %%USE_LOCAL_QT_LIBS%% --"/> - <meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/> - <meta-data android:name="android.app.load_local_libs" android:value="-- %%INSERT_LOCAL_LIBS%% --"/> - <meta-data android:name="android.app.load_local_jars" android:value="-- %%INSERT_LOCAL_JARS%% --"/> - <meta-data android:name="android.app.static_init_classes" android:value="-- %%INSERT_INIT_CLASSES%% --"/> - <!-- Messages maps --> - <meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/> - <meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/> - <meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/> - <!-- Messages maps --> - - <!-- Splash screen --> - <!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ --> - <!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ --> - <!-- Splash screen --> - - <!-- Background running --> - <!-- Warning: changing this value to true may cause unexpected crashes if the - application still try to draw after - "applicationStateChanged(Qt::ApplicationSuspended)" - signal is sent! --> - <meta-data android:name="android.app.background_running" android:value="false"/> - <!-- Background running --> - - <!-- auto screen scale factor --> - <meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/> - <!-- auto screen scale factor --> - - <!-- extract android style --> - <!-- available android:values : - * full - useful QWidget & Quick Controls 1 apps - * minimal - useful for Quick Controls 2 apps, it is much faster than "full" - * none - useful for apps that don't use any of the above Qt modules - --> - <meta-data android:name="android.app.extract_android_style" android:value="full"/> - <!-- extract android style --> - </activity> - - <!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices --> - - </application> - - <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="21"/> - <supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/> - - <!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application. - Remove the comment if you do not require these default permissions. --> - <!-- %%INSERT_PERMISSIONS --> - - <!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application. - Remove the comment if you do not require these default features. --> - <!-- %%INSERT_FEATURES --> - - <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> - -</manifest> diff --git a/src/manager/android/build.gradle b/src/manager/android/build.gradle deleted file mode 100644 index ef416b0b..00000000 --- a/src/manager/android/build.gradle +++ /dev/null @@ -1,57 +0,0 @@ -buildscript { - repositories { - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:1.1.0' - } -} - -allprojects { - repositories { - jcenter() - } -} - -apply plugin: 'com.android.application' - -dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) -} - -android { - /******************************************************* - * The following variables: - * - androidBuildToolsVersion, - * - androidCompileSdkVersion - * - qt5AndroidDir - holds the path to qt android files - * needed to build any Qt application - * on Android. - * - * are defined in gradle.properties file. This file is - * updated by QtCreator and androiddeployqt tools. - * Changing them manually might break the compilation! - *******************************************************/ - - compileSdkVersion androidCompileSdkVersion.toInteger() - - buildToolsVersion androidBuildToolsVersion - - sourceSets { - main { - manifest.srcFile 'AndroidManifest.xml' - java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] - aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] - res.srcDirs = [qt5AndroidDir + '/res', 'res'] - resources.srcDirs = ['src'] - renderscript.srcDirs = ['src'] - assets.srcDirs = ['assets'] - jniLibs.srcDirs = ['libs'] - } - } - - lintOptions { - abortOnError false - } -} diff --git a/src/manager/android/res/drawable-hdpi/icon.png b/src/manager/android/res/drawable-hdpi/icon.png Binary files differdeleted file mode 100644 index f1c9743c..00000000 --- a/src/manager/android/res/drawable-hdpi/icon.png +++ /dev/null diff --git a/src/manager/android/res/drawable-ldpi/icon.png b/src/manager/android/res/drawable-ldpi/icon.png Binary files differdeleted file mode 100644 index ba86ab00..00000000 --- a/src/manager/android/res/drawable-ldpi/icon.png +++ /dev/null diff --git a/src/manager/android/res/drawable-mdpi/icon.png b/src/manager/android/res/drawable-mdpi/icon.png Binary files differdeleted file mode 100644 index 03b85478..00000000 --- a/src/manager/android/res/drawable-mdpi/icon.png +++ /dev/null diff --git a/src/manager/android/res/values/libs.xml b/src/manager/android/res/values/libs.xml deleted file mode 100644 index 77f422cf..00000000 --- a/src/manager/android/res/values/libs.xml +++ /dev/null @@ -1,25 +0,0 @@ -<?xml version='1.0' encoding='utf-8'?> -<resources> - <array name="qt_sources"> - <item>https://download.qt.io/ministro/android/qt5/qt-5.8</item> - </array> - - <!-- The following is handled automatically by the deployment tool. It should - not be edited manually. --> - - <array name="bundled_libs"> - <!-- %%INSERT_EXTRA_LIBS%% --> - </array> - - <array name="qt_libs"> - <!-- %%INSERT_QT_LIBS%% --> - </array> - - <array name="bundled_in_lib"> - <!-- %%INSERT_BUNDLED_IN_LIB%% --> - </array> - <array name="bundled_in_assets"> - <!-- %%INSERT_BUNDLED_IN_ASSETS%% --> - </array> - -</resources> diff --git a/src/manager/manager.pro b/src/manager/manager.pro deleted file mode 100644 index 5a940a60..00000000 --- a/src/manager/manager.pro +++ /dev/null @@ -1,25 +0,0 @@ -TEMPLATE = app -TARGET = appman - -include(manager.pri) - -android { - QT *= androidextras - - ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android - - DISTFILES += \ - android/gradle/wrapper/gradle-wrapper.jar \ - android/AndroidManifest.xml \ - android/gradlew.bat \ - android/res/values/libs.xml \ - android/build.gradle \ - android/gradle/wrapper/gradle-wrapper.properties \ - android/gradlew - - # hack for neptune to get the plugin deployed -# ANDROID_EXTRA_LIBS = $$[QT_INSTALL_QML]/com/pelagicore/datasource/libqmldatasources.so - - # hack for neptune to get all relevant Qt modules deployed -# DISTFILES += android-deploy-dummy.qml -} diff --git a/src/manager/syms.txt b/src/manager/syms.txt deleted file mode 100644 index 90a495af..00000000 --- a/src/manager/syms.txt +++ /dev/null @@ -1,23 +0,0 @@ -{ - *AbstractRuntime; - *RuntimeFactory; - *Application; - *ApplicationManager; - *ApplicationInterface; - *ExecutionContainer; - *ExecutionContainerFactory; - *ExecutionContainerProcess; - *FakeApplicationManagerWindow; - extern "C++" { - AbstractRuntime::*; - Application::*; - ApplicationManager::*; - ApplicationInterface::*; - RuntimeFactory::*; - ExecutionContainer::*; - ExecutionContainerFactory::*; - ExecutionContainerProcess::*; - FakeApplicationManagerWindow::*; - Log*; - }; -}; diff --git a/src/src.pro b/src/src.pro index 58aa7357..99fc2feb 100644 --- a/src/src.pro +++ b/src/src.pro @@ -3,45 +3,95 @@ TEMPLATE = subdirs load(am-config) +common_lib.subdir = common-lib + +plugin_interfaces.subdir = plugin-interfaces + +crypto_lib.subdir = crypto-lib +crypto_lib.depends = common_lib + +application_lib.subdir = application-lib +application_lib.depends = common_lib + +notification_lib.subdir = notification-lib +notification_lib.depends = common_lib + +package_lib.subdir = package-lib +package_lib.depends = crypto_lib application_lib + +manager_lib.subdir = manager-lib +manager_lib.depends = application_lib notification_lib plugin_interfaces + +installer_lib.subdir = installer-lib +installer_lib.depends = package_lib manager_lib + +window_lib.subdir = window-lib +window_lib.depends = manager_lib + +monitor_lib.subdir = monitor-lib +monitor_lib.depends = manager_lib window_lib + +launcher_lib.subdir = launcher-lib +launcher_lib.depends = application_lib notification_lib + +main_lib.subdir = main-lib +main_lib.depends = manager_lib installer_lib window_lib monitor_lib + +launchers_qml.subdir = launchers/qml +launchers_qml.depends = launcher_lib plugin_interfaces + +tools_appman.subdir = tools/appman +tools_appman.depends = main_lib + +tools_testrunner.subdir = tools/testrunner +tools_testrunner.depends = main_lib + +tools_dumpqmltypes.subdir = tools/dumpqmltypes +tools_dumpqmltypes.depends = manager_lib installer_lib window_lib monitor_lib launcher_lib + +tools_packager.subdir = tools/packager +tools_packager.depends = package_lib + +tools_deployer.subdir = tools/deployer + +tools_controller.subdir = tools/controller +tools_controller.depends = common_lib + SUBDIRS = \ - common-lib \ - crypto-lib \ - application-lib \ - package-lib \ - -crypto-lib.depends = common-lib -application-lib.depends = common-lib -notification-lib.depends = common-lib -package-lib.depends = crypto-lib application-lib -manager-lib.depends = application-lib notification-lib plugin-interfaces -installer-lib.depends = package-lib manager-lib -window-lib.depends = manager-lib -monitor-lib.depends = manager-lib window-lib -launcher-lib.depends = application-lib notification-lib -manager.depends = manager-lib installer-lib window-lib monitor-lib -launchers.depends = launcher-lib -tools.depends = package-lib + common_lib \ + crypto_lib \ + application_lib \ + package_lib \ !tools-only { SUBDIRS += \ - plugin-interfaces \ + plugin_interfaces \ dbus \ qtHaveModule(qml):SUBDIRS += \ - notification-lib \ - manager-lib \ - installer-lib \ - window-lib \ - monitor-lib \ - manager \ + notification_lib \ + manager_lib \ + installer_lib \ + window_lib \ + main_lib \ + monitor_lib \ + tools_appman \ + # Although the testrunner is in tools we don't want to build it with tools-only + # because it is based on the manager binary + tools_testrunner \ qtHaveModule(qml):qtHaveModule(dbus):SUBDIRS += \ - launcher-lib \ - launchers + launcher_lib \ + # This tool links against everything to extract the Qml type information + tools_dumpqmltypes \ - tools.depends *= manager-lib installer-lib window-lib monitor-lib - - qtHaveModule(qml):qtHaveModule(dbus):tools.depends *= launcher-lib + multi-process:qtHaveModule(qml):qtHaveModule(dbus):SUBDIRS += \ + launchers_qml \ } -SUBDIRS += tools +!android:SUBDIRS += \ + tools_packager \ + tools_deployer \ + +qtHaveModule(dbus):SUBDIRS += \ + tools_controller \ diff --git a/src/tools/appman/appman.cpp b/src/tools/appman/appman.cpp new file mode 100644 index 00000000..aba6e487 --- /dev/null +++ b/src/tools/appman/appman.cpp @@ -0,0 +1,141 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:LGPL-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +** SPDX-License-Identifier: LGPL-3.0 +** +****************************************************************************/ + +#include <QQmlApplicationEngine> +#include <QQmlContext> + +#include "global.h" +#include "logging.h" +#include "main.h" +#include "defaultconfiguration.h" +#include "package.h" +#if !defined(AM_DISABLE_INSTALLER) +# include "sudo.h" +#endif +#include "startuptimer.h" + +#if defined(AM_TESTRUNNER) +# include "testrunner.h" +#endif + + +QT_USE_NAMESPACE_AM + +Q_DECL_EXPORT int main(int argc, char *argv[]) +{ +#if defined(Q_OS_UNIX) && defined(AM_MULTI_PROCESS) + // set a reasonable default for OSes/distros that do not set this by default + setenv("XDG_RUNTIME_DIR", "/tmp", 0); +#endif + + StartupTimer::instance()->checkpoint("entered main"); + +#if defined(AM_TESTRUNNER) + QCoreApplication::setApplicationName(qSL("ApplicationManager QML Test Runner")); +#else + QCoreApplication::setApplicationName(qSL("ApplicationManager")); +#endif + QCoreApplication::setOrganizationName(qSL("Pelagicore AG")); + QCoreApplication::setOrganizationDomain(qSL("pelagicore.com")); + QCoreApplication::setApplicationVersion(qSL(AM_VERSION)); + for (int i = 1; i < argc; ++i) { + if (strcmp("--no-dlt-logging", argv[i]) == 0) { + Logging::setDltEnabled(false); + break; + } + } + Logging::initialize(); + StartupTimer::instance()->checkpoint("after basic initialization"); + +#if !defined(AM_DISABLE_INSTALLER) + Package::ensureCorrectLocale(); + + QString error; + if (Q_UNLIKELY(!forkSudoServer(DropPrivilegesPermanently, &error))) { + qCCritical(LogSystem) << "ERROR:" << qPrintable(error); + return 2; + } + StartupTimer::instance()->checkpoint("after sudo server fork"); +#endif + + try { +#if !defined(AM_HEADLESS) + // this is needed for both WebEngine and Wayland Multi-screen rendering + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); +# if !defined(QT_NO_SESSIONMANAGER) + QGuiApplication::setFallbackSessionManagementEnabled(false); +# endif +#endif + + Main a(argc, argv); + +#if defined(AM_TESTRUNNER) + const char *additionalDescription = + "Additional testrunner commandline options can be set after the -- argument\n" \ + "Use -- -help to show all available testrunner commandline options."; + bool onlyOnePositionalArgument = false; +#else + const char *additionalDescription = nullptr; + bool onlyOnePositionalArgument = true; +#endif + + DefaultConfiguration cfg(additionalDescription, onlyOnePositionalArgument); + cfg.parse(); + + StartupTimer::instance()->checkpoint("after command line parse"); +#if defined(AM_TESTRUNNER) + TestRunner::initialize(cfg.testRunnerArguments()); +#endif + a.setup(&cfg); +#if defined(AM_TESTRUNNER) + a.qmlEngine()->rootContext()->setContextProperty("buildConfig", cfg.buildConfig()); +#endif + +#if defined(AM_TESTRUNNER) + return TestRunner::exec(a.qmlEngine()); +#else + return MainBase::exec(); +#endif + } catch (const std::exception &e) { + qCCritical(LogSystem) << "ERROR:" << e.what(); + return 2; + } +} diff --git a/src/tools/appman/appman.pro b/src/tools/appman/appman.pro new file mode 100644 index 00000000..0b9e3e46 --- /dev/null +++ b/src/tools/appman/appman.pro @@ -0,0 +1,25 @@ +TEMPLATE = app +TARGET = appman + +load(am-config) + +QT = appman_main-private + +CONFIG *= console + +#win32:LIBS += -luser32 + +SOURCES += \ + $$PWD/appman.cpp + +load(qt_tool) + +load(install-prefix) + +load(build-config) + +unix:exists($$SOURCE_DIR/.git):GIT_VERSION=$$system(cd "$$SOURCE_DIR" && git describe --tags --always --dirty 2>/dev/null) +isEmpty(GIT_VERSION):GIT_VERSION="unknown" + +createBuildConfig(_DATE_, VERSION, GIT_VERSION, SOURCE_DIR, BUILD_DIR, INSTALL_PREFIX, \ + QT_ARCH, QT_VERSION, QT, CONFIG, DEFINES, INCLUDEPATH, LIBS) diff --git a/src/tools/controller/main.cpp b/src/tools/controller/controller.cpp index 63df9897..63df9897 100644 --- a/src/tools/controller/main.cpp +++ b/src/tools/controller/controller.cpp diff --git a/src/tools/controller/controller.pro b/src/tools/controller/controller.pro index 55eae1ed..465a8a4e 100644 --- a/src/tools/controller/controller.pro +++ b/src/tools/controller/controller.pro @@ -9,7 +9,7 @@ QT *= appman_common-private CONFIG *= console SOURCES += \ - main.cpp \ + controller.cpp \ appmanif.files = ../../dbus/io.qt.applicationmanager.xml appmanif.header_flags = -i dbus-utilities.h diff --git a/src/tools/dumpqmltypes/main.cpp b/src/tools/dumpqmltypes/dumpqmltypes.cpp index 2123ae9a..2123ae9a 100644 --- a/src/tools/dumpqmltypes/main.cpp +++ b/src/tools/dumpqmltypes/dumpqmltypes.cpp diff --git a/src/tools/dumpqmltypes/dumpqmltypes.pro b/src/tools/dumpqmltypes/dumpqmltypes.pro index 73e23952..823626b9 100644 --- a/src/tools/dumpqmltypes/dumpqmltypes.pro +++ b/src/tools/dumpqmltypes/dumpqmltypes.pro @@ -15,7 +15,8 @@ QT *= \ CONFIG *= console -SOURCES += main.cpp +SOURCES += \ + dumpqmltypes.cpp load(qt_tool) diff --git a/src/tools/packager/main.cpp b/src/tools/packager/main.cpp deleted file mode 100644 index 39aefe38..00000000 --- a/src/tools/packager/main.cpp +++ /dev/null @@ -1,233 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2017 Pelagicore AG -** Contact: https://www.qt.io/licensing/ -** -** This file is part of the Pelagicore Application Manager. -** -** $QT_BEGIN_LICENSE:GPL-EXCEPT-QTAS$ -** Commercial License Usage -** Licensees holding valid commercial Qt Automotive Suite licenses may use -** this file in accordance with the commercial license agreement provided -** with the Software or, alternatively, in accordance with the terms -** contained in a written agreement between you and The Qt Company. For -** licensing terms and conditions see https://www.qt.io/terms-conditions. -** For further information use the contact form at https://www.qt.io/contact-us. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3 as published by the Free Software -** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT -** included in the packaging of this file. Please review the following -** information to ensure the GNU General Public License requirements will -** be met: https://www.gnu.org/licenses/gpl-3.0.html. -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include <QCoreApplication> -#include <QCommandLineParser> -#include <QStringList> -#include <QDebug> - -#include <stdio.h> - -#include <QtAppManCommon/exception.h> -#include <QtAppManPackage/package.h> -#include "packager.h" - -QT_USE_NAMESPACE_AM - -enum Command { - NoCommand, - CreatePackage, - DevSignPackage, - DevVerifyPackage, - StoreSignPackage, - StoreVerifyPackage, -}; - -static struct { - Command command; - const char *name; - const char *description; -} commandTable[] = { - { CreatePackage, "create-package", "Create a new package." }, - { DevSignPackage, "dev-sign-package", "Add developer signature to package." }, - { DevVerifyPackage, "dev-verify-package", "Verify developer signature on package." }, - { StoreSignPackage, "store-sign-package", "Add store signature to package." }, - { StoreVerifyPackage, "store-verify-package", "Verify store signature on package." } -}; - -static Command command(QCommandLineParser &clp) -{ - if (!clp.positionalArguments().isEmpty()) { - QByteArray cmd = clp.positionalArguments().at(0).toLatin1(); - - for (uint i = 0; i < sizeof(commandTable) / sizeof(commandTable[0]); ++i) { - if (cmd == commandTable[i].name) { - clp.clearPositionalArguments(); - clp.addPositionalArgument(cmd, commandTable[i].description, cmd); - return commandTable[i].command; - } - } - } - return NoCommand; -} - -int main(int argc, char *argv[]) -{ - Package::ensureCorrectLocale(); - - QCoreApplication::setApplicationName(qSL("ApplicationManager Packager")); - QCoreApplication::setOrganizationName(qSL("Pelagicore AG")); - QCoreApplication::setOrganizationDomain(qSL("pelagicore.com")); - QCoreApplication::setApplicationVersion(qSL(AM_VERSION)); - - QCoreApplication a(argc, argv); - - if (!Package::checkCorrectLocale()) { - fprintf(stderr, "ERROR: the packager needs a UTF-8 locale to work correctly:\n" - " even automatically switching to C.UTF-8 or en_US.UTF-8 failed.\n"); - exit(2); - } - - QString desc = qSL("\nPelagicore ApplicationManager packaging tool\n\nAvailable commands are:\n"); - uint longestName = 0; - for (uint i = 0; i < sizeof(commandTable) / sizeof(commandTable[0]); ++i) - longestName = qMax(longestName, qstrlen(commandTable[i].name)); - for (uint i = 0; i < sizeof(commandTable) / sizeof(commandTable[0]); ++i) { - desc += qSL(" %1%2 %3\n") - .arg(qL1S(commandTable[i].name), - QString(longestName - qstrlen(commandTable[i].name), qL1C(' ')), - qL1S(commandTable[i].description)); - } - - desc += qSL("\nMore information about each command can be obtained by running\n appman-packager <command> --help"); - - QCommandLineParser clp; - clp.setApplicationDescription(desc); - - clp.addHelpOption(); - clp.addVersionOption(); - - clp.addPositionalArgument(qSL("command"), qSL("The command to execute.")); - - // ignore unknown options for now -- the sub-commands may need them later - clp.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments); - - if (!clp.parse(QCoreApplication::arguments())) { - fprintf(stderr, "%s\n", qPrintable(clp.errorText())); - exit(1); - } - clp.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsOptions); - - Packager *p = nullptr; - - switch (command(clp)) { - default: - case NoCommand: - if (clp.isSet(qSL("version"))) - clp.showVersion(); - if (clp.isSet(qSL("help"))) - clp.showHelp(); - clp.showHelp(1); - break; - - case CreatePackage: - clp.addOption({ qSL("verbose"), qSL("Dump the package's meta-data header and footer information to stdout.") }); - clp.addOption({ qSL("json"), qSL("Output in JSON format instead of YAML.") }); - clp.addPositionalArgument(qSL("package"), qSL("The file name of the created package.")); - clp.addPositionalArgument(qSL("source-directory"), qSL("The package's content root directory.")); - clp.process(a); - - if (clp.positionalArguments().size() != 3) - clp.showHelp(1); - - p = Packager::create(clp.positionalArguments().at(1), - clp.positionalArguments().at(2), - clp.isSet(qSL("json"))); - break; - - case DevSignPackage: - clp.addOption({ qSL("verbose"), qSL("Dump the package's meta-data header and footer information to stdout.") }); - clp.addOption({ qSL("json"), qSL("Output in JSON format instead of YAML.") }); - clp.addPositionalArgument(qSL("package"), qSL("File name of the unsigned package (input).")); - clp.addPositionalArgument(qSL("signed-package"), qSL("File name of the signed package (output).")); - clp.addPositionalArgument(qSL("certificate"), qSL("PKCS#12 certificate file.")); - clp.addPositionalArgument(qSL("password"), qSL("Password for the PKCS#12 certificate.")); - clp.process(a); - - if (clp.positionalArguments().size() != 5) - clp.showHelp(1); - - p = Packager::developerSign(clp.positionalArguments().at(1), - clp.positionalArguments().at(2), - clp.positionalArguments().at(3), - clp.positionalArguments().at(4), - clp.isSet(qSL("json"))); - break; - - case DevVerifyPackage: - clp.addOption({ qSL("verbose"), qSL("Print details regarding the verification to stdout.") }); - clp.addPositionalArgument(qSL("package"), qSL("File name of the signed package (input).")); - clp.addPositionalArgument(qSL("certificates"), qSL("The developer's CA certificate file(s)."), qSL("certificates...")); - clp.process(a); - - if (clp.positionalArguments().size() < 3) - clp.showHelp(1); - - p = Packager::developerVerify(clp.positionalArguments().at(1), - clp.positionalArguments().mid(2)); - break; - - case StoreSignPackage: - clp.addOption({ qSL("verbose"), qSL("Dump the package's meta-data header and footer information to stdout.") }); - clp.addOption({ qSL("json"), qSL("Output in JSON format instead of YAML.") }); - clp.addPositionalArgument(qSL("package"), qSL("File name of the unsigned package (input).")); - clp.addPositionalArgument(qSL("signed-package"), qSL("File name of the signed package (output).")); - clp.addPositionalArgument(qSL("certificate"), qSL("PKCS#12 certificate file.")); - clp.addPositionalArgument(qSL("password"), qSL("Password for the PKCS#12 certificate.")); - clp.addPositionalArgument(qSL("hardware-id"), qSL("Unique hardware id to which this package gets bound.")); - clp.process(a); - - if (clp.positionalArguments().size() != 6) - clp.showHelp(1); - - p = Packager::storeSign(clp.positionalArguments().at(1), - clp.positionalArguments().at(2), - clp.positionalArguments().at(3), - clp.positionalArguments().at(4), - clp.positionalArguments().at(5), - clp.isSet(qSL("json"))); - break; - - case StoreVerifyPackage: - clp.addOption({ qSL("verbose"), qSL("Print details regarding the verification to stdout.") }); - clp.addPositionalArgument(qSL("package"), qSL("File name of the signed package (input).")); - clp.addPositionalArgument(qSL("certificates"), qSL("Store CA certificate file(s)."), qSL("certificates...")); - clp.addPositionalArgument(qSL("hardware-id"), qSL("Unique hardware id to which this package was bound.")); - clp.process(a); - - if (clp.positionalArguments().size() < 4) - clp.showHelp(1); - - p = Packager::storeVerify(clp.positionalArguments().at(1), - clp.positionalArguments().mid(2, clp.positionalArguments().size() - 2), - *--clp.positionalArguments().cend()); - break; - } - - if (!p) - return 2; - try { - p->execute(); - if (clp.isSet(qSL("verbose")) && !p->output().isEmpty()) - fprintf(stdout, "%s\n", qPrintable(p->output())); - return p->resultCode(); - } catch (const Exception &e) { - fprintf(stderr, "ERROR: %s\n", qPrintable(e.errorString())); - return 1; - } -} diff --git a/src/tools/packager/packager.cpp b/src/tools/packager/packager.cpp index b738f0c8..f595b595 100644 --- a/src/tools/packager/packager.cpp +++ b/src/tools/packager/packager.cpp @@ -26,285 +26,208 @@ ** ****************************************************************************/ -#include <QFile> -#include <QFileInfo> -#include <QUrl> -#include <QRegExp> -#include <QDirIterator> -#include <QMessageAuthenticationCode> -#include <QJsonDocument> -#include <QTemporaryDir> +#include <QCoreApplication> +#include <QCommandLineParser> +#include <QStringList> +#include <QDebug> #include <stdio.h> -#include <stdlib.h> -#include "exception.h" -#include "signature.h" -#include "qtyaml.h" -#include "application.h" -#include "installationreport.h" -#include "yamlapplicationscanner.h" -#include "packageextractor.h" -#include "packagecreator.h" - -#include "packager.h" +#include <QtAppManCommon/exception.h> +#include <QtAppManPackage/package.h> +#include "packagingjob.h" QT_USE_NAMESPACE_AM -// this corresponds to the -b parameter for mkfs.ext2 in sudo.cpp -static const int Ext2BlockSize = 1024; - - -Packager *Packager::create(const QString &destinationName, const QString &sourceDir, bool asJson) -{ - Packager *p = new Packager(); - p->m_mode = Create; - p->m_asJson = asJson; - p->m_destinationName = destinationName; - p->m_sourceDir = sourceDir; - return p; -} - -Packager *Packager::developerSign(const QString &sourceName, const QString &destinationName, - const QString &certificateFile, const QString &passPhrase, - bool asJson) -{ - Packager *p = new Packager(); - p->m_mode = DeveloperSign; - p->m_asJson = asJson; - p->m_sourceName = sourceName; - p->m_destinationName = destinationName; - p->m_passphrase = passPhrase; - p->m_certificateFiles = QStringList { certificateFile }; - return p; -} - -Packager *Packager::developerVerify(const QString &sourceName, const QStringList &certificateFiles) -{ - Packager *p = new Packager(); - p->m_mode = DeveloperVerify; - p->m_sourceName = sourceName; - p->m_certificateFiles = certificateFiles; - return p; -} - -Packager *Packager::storeSign(const QString &sourceName, const QString &destinationName, - const QString &certificateFile, const QString &passPhrase, - const QString &hardwareId, bool asJson) +enum Command { + NoCommand, + CreatePackage, + DevSignPackage, + DevVerifyPackage, + StoreSignPackage, + StoreVerifyPackage, +}; + +static struct { + Command command; + const char *name; + const char *description; +} commandTable[] = { + { CreatePackage, "create-package", "Create a new package." }, + { DevSignPackage, "dev-sign-package", "Add developer signature to package." }, + { DevVerifyPackage, "dev-verify-package", "Verify developer signature on package." }, + { StoreSignPackage, "store-sign-package", "Add store signature to package." }, + { StoreVerifyPackage, "store-verify-package", "Verify store signature on package." } +}; + +static Command command(QCommandLineParser &clp) { - Packager *p = new Packager(); - p->m_mode = StoreSign; - p->m_asJson = asJson; - p->m_sourceName = sourceName; - p->m_destinationName = destinationName; - p->m_passphrase = passPhrase; - p->m_certificateFiles = QStringList { certificateFile }; - p->m_hardwareId = hardwareId; - return p; -} - -Packager *Packager::storeVerify(const QString &sourceName, const QStringList &certificateFiles, const QString &hardwareId) -{ - Packager *p = new Packager(); - p->m_mode = StoreVerify; - p->m_sourceName = sourceName; - p->m_certificateFiles = certificateFiles; - p->m_hardwareId = hardwareId; - return p; -} - -QString Packager::output() const -{ - return m_output; -} - -int Packager::resultCode() const -{ - return m_resultCode; + if (!clp.positionalArguments().isEmpty()) { + QByteArray cmd = clp.positionalArguments().at(0).toLatin1(); + + for (uint i = 0; i < sizeof(commandTable) / sizeof(commandTable[0]); ++i) { + if (cmd == commandTable[i].name) { + clp.clearPositionalArguments(); + clp.addPositionalArgument(cmd, commandTable[i].description, cmd); + return commandTable[i].command; + } + } + } + return NoCommand; } -Packager::Packager() -{ } - -void Packager::execute() Q_DECL_NOEXCEPT_EXPR(false) +int main(int argc, char *argv[]) { - switch (m_mode) { - case Create: { - if (m_destinationName.isEmpty()) - throw Exception(Error::Package, "no destination package name given"); - - QFile destination(m_destinationName); - if (!destination.open(QIODevice::WriteOnly | QIODevice::Truncate)) - throw Exception(destination, "could not create package file"); - - QString canonicalDestination = QFileInfo(destination).canonicalFilePath(); - - QDir source(m_sourceDir); - if (!source.exists()) - throw Exception(Error::Package, "source %1 is not a directory").arg(m_sourceDir); - - // check metadata - YamlApplicationScanner yas; - QString infoName = yas.metaDataFileName(); - QScopedPointer<Application> app(yas.scan(source.absoluteFilePath(infoName))); - - // build report - InstallationReport report(app->id()); - report.addFile(infoName); + Package::ensureCorrectLocale(); - if (!QFile::exists(source.absoluteFilePath(app->icon()))) - throw Exception(Error::Package, "missing the 'icon.png' file"); - report.addFile(qSL("icon.png")); + QCoreApplication::setApplicationName(qSL("ApplicationManager Packager")); + QCoreApplication::setOrganizationName(qSL("Pelagicore AG")); + QCoreApplication::setOrganizationDomain(qSL("pelagicore.com")); + QCoreApplication::setApplicationVersion(qSL(AM_VERSION)); - // check executable - if (!QFile::exists(source.absoluteFilePath(app->codeFilePath()))) - throw Exception(Error::Package, "missing the file referenced by the 'code' field"); + QCoreApplication a(argc, argv); - quint64 estimatedImageSize = 0; - QString canonicalSourcePath = source.canonicalPath(); - QDirIterator it(source.absolutePath(), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); - - while (it.hasNext()) { - it.next(); - QFileInfo entryInfo = it.fileInfo(); - QString entryPath = entryInfo.canonicalFilePath(); + if (!Package::checkCorrectLocale()) { + fprintf(stderr, "ERROR: the packager needs a UTF-8 locale to work correctly:\n" + " even automatically switching to C.UTF-8 or en_US.UTF-8 failed.\n"); + exit(2); + } - // do not package the package itself, in case someone builds the package within the source dir - if (canonicalDestination == entryPath) - continue; + QString desc = qSL("\nPelagicore ApplicationManager packaging tool\n\nAvailable commands are:\n"); + uint longestName = 0; + for (uint i = 0; i < sizeof(commandTable) / sizeof(commandTable[0]); ++i) + longestName = qMax(longestName, qstrlen(commandTable[i].name)); + for (uint i = 0; i < sizeof(commandTable) / sizeof(commandTable[0]); ++i) { + desc += qSL(" %1%2 %3\n") + .arg(qL1S(commandTable[i].name), + QString(longestName - qstrlen(commandTable[i].name), qL1C(' ')), + qL1S(commandTable[i].description)); + } - if (!entryPath.startsWith(canonicalSourcePath)) - throw Exception(Error::Package, "file %1 is not inside the source directory %2").arg(entryPath).arg(canonicalSourcePath); + desc += qSL("\nMore information about each command can be obtained by running\n appman-packager <command> --help"); - // QDirIterator::filePath() returns absolute paths, although the naming suggests otherwise - entryPath = entryPath.mid(canonicalSourcePath.size() + 1); + QCommandLineParser clp; + clp.setApplicationDescription(desc); - if (entryInfo.fileName().startsWith(qL1S("--PACKAGE-"))) - throw Exception(Error::Package, "file names starting with --PACKAGE- are reserved by the packager (found: %1)").arg(entryPath); + clp.addHelpOption(); + clp.addVersionOption(); - estimatedImageSize += (entryInfo.size() + Ext2BlockSize - 1) / Ext2BlockSize; + clp.addPositionalArgument(qSL("command"), qSL("The command to execute.")); - if (entryPath != infoName && entryPath != qL1S("icon.png")) - report.addFile(entryPath); - } + // ignore unknown options for now -- the sub-commands may need them later + clp.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsPositionalArguments); - // we have the estimatedImageSize for the raw content now, but we need to add the inode - // overhead still. This algorithm comes from buildroot: - // http://git.buildroot.net/buildroot/tree/package/mke2img/mke2img - estimatedImageSize = (500 + (estimatedImageSize + report.files().count() + 400 / 8) * 11 / 10) * Ext2BlockSize; - report.setDiskSpaceUsed(estimatedImageSize); + if (!clp.parse(QCoreApplication::arguments())) { + fprintf(stderr, "%s\n", qPrintable(clp.errorText())); + exit(1); + } + clp.setOptionsAfterPositionalArgumentsMode(QCommandLineParser::ParseAsOptions); - // finally create the package - PackageCreator creator(source, &destination, report); - if (!creator.create()) - throw Exception(Error::Package, "could not create package %1: %2").arg(app->id()).arg(creator.errorString()); + PackagingJob *p = nullptr; - QVariantMap md = creator.metaData(); - m_output = m_asJson ? QJsonDocument::fromVariant(md).toJson().constData() - : QtYaml::yamlFromVariantDocuments({ md }).constData(); + switch (command(clp)) { + default: + case NoCommand: + if (clp.isSet(qSL("version"))) + clp.showVersion(); + if (clp.isSet(qSL("help"))) + clp.showHelp(); + clp.showHelp(1); break; - } - case DeveloperSign: - case DeveloperVerify: - case StoreSign: - case StoreVerify: { - if (!QFile::exists(m_sourceName)) - throw Exception(Error::Package, "package file %1 does not exist").arg(m_sourceName); - - // read certificates - QList<QByteArray> certificates; - for (const QString &cert : qAsConst(m_certificateFiles)) { - QFile cf(cert); - if (!cf.open(QIODevice::ReadOnly)) - throw Exception(cf, "could not open certificate file"); - certificates << cf.readAll(); - } - // create temporary dir for extraction - QTemporaryDir tmp; - if (!tmp.isValid()) - throw Exception(Error::Package, "could not create temporary directory %1").arg(tmp.path()); - - // extract source - PackageExtractor extractor(QUrl::fromLocalFile(m_sourceName), tmp.path()); - if (!extractor.extract()) - throw Exception(Error::Package, "could not extract package %1: %2").arg(m_sourceName).arg(extractor.errorString()); - - InstallationReport report = extractor.installationReport(); - - // check signatures - if (m_mode == DeveloperVerify) { - if (report.developerSignature().isEmpty()) { - m_output = qSL("no developer signature"); - m_resultCode = 1; - } else { - Signature sig(report.digest()); - if (!sig.verify(report.developerSignature(), certificates)) { - m_output = qSL("invalid developer signature (") + sig.errorString() + qSL(")"); - m_resultCode = 2; - } else { - m_output = qSL("valid developer signature"); - } - } - break; // done with DeveloperVerify - - } else if (m_mode == StoreVerify) { - if (report.storeSignature().isEmpty()) { - m_output = qSL("no store signature"); - m_resultCode = 1; - } else { - QByteArray digestPlusId = QMessageAuthenticationCode::hash(report.digest(), m_hardwareId.toUtf8(), QCryptographicHash::Sha256); - Signature sig(digestPlusId); - if (!sig.verify(report.storeSignature(), certificates)) { - m_output = qSL("invalid store signature (") + sig.errorString() + qSL(")"); - m_resultCode = 2; - } else { - m_output = qSL("valid store signature"); - } + case CreatePackage: + clp.addOption({ qSL("verbose"), qSL("Dump the package's meta-data header and footer information to stdout.") }); + clp.addOption({ qSL("json"), qSL("Output in JSON format instead of YAML.") }); + clp.addPositionalArgument(qSL("package"), qSL("The file name of the created package.")); + clp.addPositionalArgument(qSL("source-directory"), qSL("The package's content root directory.")); + clp.process(a); - } - break; // done with StoreVerify - } + if (clp.positionalArguments().size() != 3) + clp.showHelp(1); - // create a signed package - if (m_destinationName.isEmpty()) - throw Exception(Error::Package, "no destination package name given"); + p = PackagingJob::create(clp.positionalArguments().at(1), + clp.positionalArguments().at(2), + clp.isSet(qSL("json"))); + break; - QFile destination(m_destinationName); - if (!destination.open(QIODevice::WriteOnly | QIODevice::Truncate)) - throw Exception(destination, "could not create package file"); + case DevSignPackage: + clp.addOption({ qSL("verbose"), qSL("Dump the package's meta-data header and footer information to stdout.") }); + clp.addOption({ qSL("json"), qSL("Output in JSON format instead of YAML.") }); + clp.addPositionalArgument(qSL("package"), qSL("File name of the unsigned package (input).")); + clp.addPositionalArgument(qSL("signed-package"), qSL("File name of the signed package (output).")); + clp.addPositionalArgument(qSL("certificate"), qSL("PKCS#12 certificate file.")); + clp.addPositionalArgument(qSL("password"), qSL("Password for the PKCS#12 certificate.")); + clp.process(a); + + if (clp.positionalArguments().size() != 5) + clp.showHelp(1); + + p = PackagingJob::developerSign(clp.positionalArguments().at(1), + clp.positionalArguments().at(2), + clp.positionalArguments().at(3), + clp.positionalArguments().at(4), + clp.isSet(qSL("json"))); + break; - PackageCreator creator(tmp.path(), &destination, report); + case DevVerifyPackage: + clp.addOption({ qSL("verbose"), qSL("Print details regarding the verification to stdout.") }); + clp.addPositionalArgument(qSL("package"), qSL("File name of the signed package (input).")); + clp.addPositionalArgument(qSL("certificates"), qSL("The developer's CA certificate file(s)."), qSL("certificates...")); + clp.process(a); - if (certificates.size() != 1) - throw Exception(Error::Package, "cannot sign packages with more than one certificate"); + if (clp.positionalArguments().size() < 3) + clp.showHelp(1); - if (m_mode == DeveloperSign) { - Signature sig(report.digest()); - QByteArray signature = sig.create(certificates.first(), m_passphrase.toUtf8()); + p = PackagingJob::developerVerify(clp.positionalArguments().at(1), + clp.positionalArguments().mid(2)); + break; - if (signature.isEmpty()) - throw Exception(Error::Package, "could not create signature: %1").arg(sig.errorString()); - report.setDeveloperSignature(signature); - } else if (m_mode == StoreSign) { - QByteArray digestPlusId = QMessageAuthenticationCode::hash(report.digest(), m_hardwareId.toUtf8(), QCryptographicHash::Sha256); - Signature sig(digestPlusId); - QByteArray signature = sig.create(certificates.first(), m_passphrase.toUtf8()); + case StoreSignPackage: + clp.addOption({ qSL("verbose"), qSL("Dump the package's meta-data header and footer information to stdout.") }); + clp.addOption({ qSL("json"), qSL("Output in JSON format instead of YAML.") }); + clp.addPositionalArgument(qSL("package"), qSL("File name of the unsigned package (input).")); + clp.addPositionalArgument(qSL("signed-package"), qSL("File name of the signed package (output).")); + clp.addPositionalArgument(qSL("certificate"), qSL("PKCS#12 certificate file.")); + clp.addPositionalArgument(qSL("password"), qSL("Password for the PKCS#12 certificate.")); + clp.addPositionalArgument(qSL("hardware-id"), qSL("Unique hardware id to which this package gets bound.")); + clp.process(a); + + if (clp.positionalArguments().size() != 6) + clp.showHelp(1); + + p = PackagingJob::storeSign(clp.positionalArguments().at(1), + clp.positionalArguments().at(2), + clp.positionalArguments().at(3), + clp.positionalArguments().at(4), + clp.positionalArguments().at(5), + clp.isSet(qSL("json"))); + break; - if (signature.isEmpty()) - throw Exception(Error::Package, "could not create signature: %1").arg(sig.errorString()); - report.setStoreSignature(signature); - } + case StoreVerifyPackage: + clp.addOption({ qSL("verbose"), qSL("Print details regarding the verification to stdout.") }); + clp.addPositionalArgument(qSL("package"), qSL("File name of the signed package (input).")); + clp.addPositionalArgument(qSL("certificates"), qSL("Store CA certificate file(s)."), qSL("certificates...")); + clp.addPositionalArgument(qSL("hardware-id"), qSL("Unique hardware id to which this package was bound.")); + clp.process(a); - if (!creator.create()) - throw Exception(Error::Package, "could not create package %1: %2").arg(m_destinationName).arg(creator.errorString()); + if (clp.positionalArguments().size() < 4) + clp.showHelp(1); - QVariantMap md = creator.metaData(); - m_output = m_asJson ? QJsonDocument::fromVariant(md).toJson().constData() - : QtYaml::yamlFromVariantDocuments({ md }).constData(); + p = PackagingJob::storeVerify(clp.positionalArguments().at(1), + clp.positionalArguments().mid(2, clp.positionalArguments().size() - 2), + *--clp.positionalArguments().cend()); break; } - default: - throw Exception("invalid mode"); + + if (!p) + return 2; + try { + p->execute(); + if (clp.isSet(qSL("verbose")) && !p->output().isEmpty()) + fprintf(stdout, "%s\n", qPrintable(p->output())); + return p->resultCode(); + } catch (const Exception &e) { + fprintf(stderr, "ERROR: %s\n", qPrintable(e.errorString())); + return 1; } } diff --git a/src/tools/packager/packager.pro b/src/tools/packager/packager.pro index 2e6fab05..fba890af 100644 --- a/src/tools/packager/packager.pro +++ b/src/tools/packager/packager.pro @@ -13,11 +13,11 @@ QT *= \ CONFIG *= console SOURCES += \ - main.cpp \ - packager.cpp + packager.cpp \ + packagingjob.cpp \ HEADERS += \ - packager.h + packagingjob.h \ load(qt_tool) diff --git a/src/tools/packager/packagingjob.cpp b/src/tools/packager/packagingjob.cpp new file mode 100644 index 00000000..9551fe3e --- /dev/null +++ b/src/tools/packager/packagingjob.cpp @@ -0,0 +1,310 @@ +/**************************************************************************** +** +** Copyright (C) 2017 Pelagicore AG +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Pelagicore Application Manager. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT-QTAS$ +** Commercial License Usage +** Licensees holding valid commercial Qt Automotive Suite licenses may use +** this file in accordance with the commercial license agreement provided +** with the Software or, alternatively, in accordance with the terms +** contained in a written agreement between you and The Qt Company. For +** licensing terms and conditions see https://www.qt.io/terms-conditions. +** For further information use the contact form at https://www.qt.io/contact-us. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3 as published by the Free Software +** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QFile> +#include <QFileInfo> +#include <QUrl> +#include <QRegExp> +#include <QDirIterator> +#include <QMessageAuthenticationCode> +#include <QJsonDocument> +#include <QTemporaryDir> + +#include <stdio.h> +#include <stdlib.h> + +#include "exception.h" +#include "signature.h" +#include "qtyaml.h" +#include "application.h" +#include "installationreport.h" +#include "yamlapplicationscanner.h" +#include "packageextractor.h" +#include "packagecreator.h" + +#include "packagingjob.h" + +QT_USE_NAMESPACE_AM + +// this corresponds to the -b parameter for mkfs.ext2 in sudo.cpp +static const int Ext2BlockSize = 1024; + + +PackagingJob *PackagingJob::create(const QString &destinationName, const QString &sourceDir, bool asJson) +{ + PackagingJob *p = new PackagingJob(); + p->m_mode = Create; + p->m_asJson = asJson; + p->m_destinationName = destinationName; + p->m_sourceDir = sourceDir; + return p; +} + +PackagingJob *PackagingJob::developerSign(const QString &sourceName, const QString &destinationName, + const QString &certificateFile, const QString &passPhrase, + bool asJson) +{ + PackagingJob *p = new PackagingJob(); + p->m_mode = DeveloperSign; + p->m_asJson = asJson; + p->m_sourceName = sourceName; + p->m_destinationName = destinationName; + p->m_passphrase = passPhrase; + p->m_certificateFiles = QStringList { certificateFile }; + return p; +} + +PackagingJob *PackagingJob::developerVerify(const QString &sourceName, const QStringList &certificateFiles) +{ + PackagingJob *p = new PackagingJob(); + p->m_mode = DeveloperVerify; + p->m_sourceName = sourceName; + p->m_certificateFiles = certificateFiles; + return p; +} + +PackagingJob *PackagingJob::storeSign(const QString &sourceName, const QString &destinationName, + const QString &certificateFile, const QString &passPhrase, + const QString &hardwareId, bool asJson) +{ + PackagingJob *p = new PackagingJob(); + p->m_mode = StoreSign; + p->m_asJson = asJson; + p->m_sourceName = sourceName; + p->m_destinationName = destinationName; + p->m_passphrase = passPhrase; + p->m_certificateFiles = QStringList { certificateFile }; + p->m_hardwareId = hardwareId; + return p; +} + +PackagingJob *PackagingJob::storeVerify(const QString &sourceName, const QStringList &certificateFiles, const QString &hardwareId) +{ + PackagingJob *p = new PackagingJob(); + p->m_mode = StoreVerify; + p->m_sourceName = sourceName; + p->m_certificateFiles = certificateFiles; + p->m_hardwareId = hardwareId; + return p; +} + +QString PackagingJob::output() const +{ + return m_output; +} + +int PackagingJob::resultCode() const +{ + return m_resultCode; +} + +PackagingJob::PackagingJob() +{ } + +void PackagingJob::execute() Q_DECL_NOEXCEPT_EXPR(false) +{ + switch (m_mode) { + case Create: { + if (m_destinationName.isEmpty()) + throw Exception(Error::Package, "no destination package name given"); + + QFile destination(m_destinationName); + if (!destination.open(QIODevice::WriteOnly | QIODevice::Truncate)) + throw Exception(destination, "could not create package file"); + + QString canonicalDestination = QFileInfo(destination).canonicalFilePath(); + + QDir source(m_sourceDir); + if (!source.exists()) + throw Exception(Error::Package, "source %1 is not a directory").arg(m_sourceDir); + + // check metadata + YamlApplicationScanner yas; + QString infoName = yas.metaDataFileName(); + QScopedPointer<Application> app(yas.scan(source.absoluteFilePath(infoName))); + + // build report + InstallationReport report(app->id()); + report.addFile(infoName); + + if (!QFile::exists(source.absoluteFilePath(app->icon()))) + throw Exception(Error::Package, "missing the 'icon.png' file"); + report.addFile(qSL("icon.png")); + + // check executable + if (!QFile::exists(source.absoluteFilePath(app->codeFilePath()))) + throw Exception(Error::Package, "missing the file referenced by the 'code' field"); + + quint64 estimatedImageSize = 0; + QString canonicalSourcePath = source.canonicalPath(); + QDirIterator it(source.absolutePath(), QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot, QDirIterator::Subdirectories); + + while (it.hasNext()) { + it.next(); + QFileInfo entryInfo = it.fileInfo(); + QString entryPath = entryInfo.canonicalFilePath(); + + // do not package the package itself, in case someone builds the package within the source dir + if (canonicalDestination == entryPath) + continue; + + if (!entryPath.startsWith(canonicalSourcePath)) + throw Exception(Error::Package, "file %1 is not inside the source directory %2").arg(entryPath).arg(canonicalSourcePath); + + // QDirIterator::filePath() returns absolute paths, although the naming suggests otherwise + entryPath = entryPath.mid(canonicalSourcePath.size() + 1); + + if (entryInfo.fileName().startsWith(qL1S("--PACKAGE-"))) + throw Exception(Error::Package, "file names starting with --PACKAGE- are reserved by the packager (found: %1)").arg(entryPath); + + estimatedImageSize += (entryInfo.size() + Ext2BlockSize - 1) / Ext2BlockSize; + + if (entryPath != infoName && entryPath != qL1S("icon.png")) + report.addFile(entryPath); + } + + // we have the estimatedImageSize for the raw content now, but we need to add the inode + // overhead still. This algorithm comes from buildroot: + // http://git.buildroot.net/buildroot/tree/package/mke2img/mke2img + estimatedImageSize = (500 + (estimatedImageSize + report.files().count() + 400 / 8) * 11 / 10) * Ext2BlockSize; + report.setDiskSpaceUsed(estimatedImageSize); + + // finally create the package + PackageCreator creator(source, &destination, report); + if (!creator.create()) + throw Exception(Error::Package, "could not create package %1: %2").arg(app->id()).arg(creator.errorString()); + + QVariantMap md = creator.metaData(); + m_output = m_asJson ? QJsonDocument::fromVariant(md).toJson().constData() + : QtYaml::yamlFromVariantDocuments({ md }).constData(); + break; + } + case DeveloperSign: + case DeveloperVerify: + case StoreSign: + case StoreVerify: { + if (!QFile::exists(m_sourceName)) + throw Exception(Error::Package, "package file %1 does not exist").arg(m_sourceName); + + // read certificates + QList<QByteArray> certificates; + for (const QString &cert : qAsConst(m_certificateFiles)) { + QFile cf(cert); + if (!cf.open(QIODevice::ReadOnly)) + throw Exception(cf, "could not open certificate file"); + certificates << cf.readAll(); + } + + // create temporary dir for extraction + QTemporaryDir tmp; + if (!tmp.isValid()) + throw Exception(Error::Package, "could not create temporary directory %1").arg(tmp.path()); + + // extract source + PackageExtractor extractor(QUrl::fromLocalFile(m_sourceName), tmp.path()); + if (!extractor.extract()) + throw Exception(Error::Package, "could not extract package %1: %2").arg(m_sourceName).arg(extractor.errorString()); + + InstallationReport report = extractor.installationReport(); + + // check signatures + if (m_mode == DeveloperVerify) { + if (report.developerSignature().isEmpty()) { + m_output = qSL("no developer signature"); + m_resultCode = 1; + } else { + Signature sig(report.digest()); + if (!sig.verify(report.developerSignature(), certificates)) { + m_output = qSL("invalid developer signature (") + sig.errorString() + qSL(")"); + m_resultCode = 2; + } else { + m_output = qSL("valid developer signature"); + } + } + break; // done with DeveloperVerify + + } else if (m_mode == StoreVerify) { + if (report.storeSignature().isEmpty()) { + m_output = qSL("no store signature"); + m_resultCode = 1; + } else { + QByteArray digestPlusId = QMessageAuthenticationCode::hash(report.digest(), m_hardwareId.toUtf8(), QCryptographicHash::Sha256); + Signature sig(digestPlusId); + if (!sig.verify(report.storeSignature(), certificates)) { + m_output = qSL("invalid store signature (") + sig.errorString() + qSL(")"); + m_resultCode = 2; + } else { + m_output = qSL("valid store signature"); + } + + } + break; // done with StoreVerify + } + + // create a signed package + if (m_destinationName.isEmpty()) + throw Exception(Error::Package, "no destination package name given"); + + QFile destination(m_destinationName); + if (!destination.open(QIODevice::WriteOnly | QIODevice::Truncate)) + throw Exception(destination, "could not create package file"); + + PackageCreator creator(tmp.path(), &destination, report); + + if (certificates.size() != 1) + throw Exception(Error::Package, "cannot sign packages with more than one certificate"); + + if (m_mode == DeveloperSign) { + Signature sig(report.digest()); + QByteArray signature = sig.create(certificates.first(), m_passphrase.toUtf8()); + + if (signature.isEmpty()) + throw Exception(Error::Package, "could not create signature: %1").arg(sig.errorString()); + report.setDeveloperSignature(signature); + } else if (m_mode == StoreSign) { + QByteArray digestPlusId = QMessageAuthenticationCode::hash(report.digest(), m_hardwareId.toUtf8(), QCryptographicHash::Sha256); + Signature sig(digestPlusId); + QByteArray signature = sig.create(certificates.first(), m_passphrase.toUtf8()); + + if (signature.isEmpty()) + throw Exception(Error::Package, "could not create signature: %1").arg(sig.errorString()); + report.setStoreSignature(signature); + } + + if (!creator.create()) + throw Exception(Error::Package, "could not create package %1: %2").arg(m_destinationName).arg(creator.errorString()); + + QVariantMap md = creator.metaData(); + m_output = m_asJson ? QJsonDocument::fromVariant(md).toJson().constData() + : QtYaml::yamlFromVariantDocuments({ md }).constData(); + break; + } + default: + throw Exception("invalid mode"); + } +} diff --git a/src/tools/packager/packager.h b/src/tools/packager/packagingjob.h index 22e5376f..9b22c90a 100644 --- a/src/tools/packager/packager.h +++ b/src/tools/packager/packagingjob.h @@ -32,21 +32,21 @@ #include <QByteArray> #include <QString> -class Packager +class PackagingJob { public: - static Packager *create(const QString &destinationName, const QString &sourceDir, bool asJson = false); + static PackagingJob *create(const QString &destinationName, const QString &sourceDir, bool asJson = false); - static Packager *developerSign(const QString &sourceName, const QString &destinationName, - const QString &certificateFile, const QString &passPhrase, - bool asJson = false); - static Packager *developerVerify(const QString &sourceName, const QStringList &certificateFiles); + static PackagingJob *developerSign(const QString &sourceName, const QString &destinationName, + const QString &certificateFile, const QString &passPhrase, + bool asJson = false); + static PackagingJob *developerVerify(const QString &sourceName, const QStringList &certificateFiles); - static Packager *storeSign(const QString &sourceName, const QString &destinationName, - const QString &certificateFile, const QString &passPhrase, - const QString &hardwareId, bool asJson = false); - static Packager *storeVerify(const QString &sourceName, const QStringList &certificateFiles, - const QString &hardwareId); + static PackagingJob *storeSign(const QString &sourceName, const QString &destinationName, + const QString &certificateFile, const QString &passPhrase, + const QString &hardwareId, bool asJson = false); + static PackagingJob *storeVerify(const QString &sourceName, const QStringList &certificateFiles, + const QString &hardwareId); void execute() Q_DECL_NOEXCEPT_EXPR(false); @@ -54,7 +54,7 @@ public: int resultCode() const; private: - Packager(); + PackagingJob(); enum Mode { Create, diff --git a/src/tools/testrunner/testrunner.pro b/src/tools/testrunner/testrunner.pro index 432e2409..02825d1f 100644 --- a/src/tools/testrunner/testrunner.pro +++ b/src/tools/testrunner/testrunner.pro @@ -1,9 +1,10 @@ -TEMPLATE = app +include(../appman/appman.pro) + TARGET = appman-qmltestrunner DEFINES += AM_TESTRUNNER -include($$PWD/../../manager/manager.pri) +CONFIG *= console QT += qmltest qmltest-private diff --git a/src/tools/tools.pro b/src/tools/tools.pro deleted file mode 100644 index 181ebdc1..00000000 --- a/src/tools/tools.pro +++ /dev/null @@ -1,18 +0,0 @@ - -TEMPLATE = subdirs - -!tools-only { - # Although the testrunner is in tools we don't want to build it with tools-only - # because it is based on the manager binary - SUBDIRS += testrunner - - # This tool links against everything to extract the Qml type information - qtHaveModule(qml):qtHaveModule(dbus):SUBDIRS += dumpqmltypes -} - -!android:SUBDIRS += \ - packager \ - deployer \ - -qtHaveModule(dbus):SUBDIRS += \ - controller \ diff --git a/src/window-lib/waylandcompositor.h b/src/window-lib/waylandcompositor.h index 9cc77ddb..79f863e1 100644 --- a/src/window-lib/waylandcompositor.h +++ b/src/window-lib/waylandcompositor.h @@ -42,6 +42,10 @@ #pragma once +#include <QtAppManCommon/global.h> + +#if defined(AM_MULTI_PROCESS) + #include <QWaylandQuickCompositor> #include <QtAppManWindow/windowmanager.h> @@ -155,3 +159,5 @@ private: }; QT_END_NAMESPACE_AM + +#endif // AM_MULTIPROCESS diff --git a/sync.profile b/sync.profile index 01485f1b..4f5281ab 100644 --- a/sync.profile +++ b/sync.profile @@ -5,6 +5,7 @@ "QtAppManPackage" => "$basedir/src/package-lib", "QtAppManNotification" => "$basedir/src/notification-lib", "QtAppManManager" => "$basedir/src/manager-lib", + "QtAppManMain" => "$basedir/src/main-lib", "QtAppManWindow" => "$basedir/src/window-lib", "QtAppManInstaller" => "$basedir/src/installer-lib", "QtAppManLauncher" => "$basedir/src/launcher-lib", diff --git a/tests/packager-tool/packager-tool.pro b/tests/packager-tool/packager-tool.pro index a7b8d472..214995bb 100644 --- a/tests/packager-tool/packager-tool.pro +++ b/tests/packager-tool/packager-tool.pro @@ -13,6 +13,6 @@ QT *= \ appman_installer-private \ INCLUDEPATH += ../../src/tools/packager -SOURCES += ../../src/tools/packager/packager.cpp +SOURCES += ../../src/tools/packager/packagingjob.cpp SOURCES += tst_packager-tool.cpp diff --git a/tests/packager-tool/tst_packager-tool.cpp b/tests/packager-tool/tst_packager-tool.cpp index 5d4404d8..652f453d 100644 --- a/tests/packager-tool/tst_packager-tool.cpp +++ b/tests/packager-tool/tst_packager-tool.cpp @@ -35,7 +35,7 @@ #include "application.h" #include "qtyaml.h" #include "exception.h" -#include "packager.h" +#include "packagingjob.h" #include "applicationinstaller.h" #include "qmlinprocessruntime.h" #include "runtimefactory.h" @@ -135,7 +135,7 @@ void tst_PackagerTool::initTestCase() // exceptions are nice -- just not for unit testing :) -static bool packagerCheck(Packager *p, QString &errorString) +static bool packagerCheck(PackagingJob *p, QString &errorString) { bool result = false; try { @@ -156,39 +156,39 @@ void tst_PackagerTool::test() QString hardwareId = "foobar"; // no valid destination - QVERIFY(!packagerCheck(Packager::create(pathTo("test.appkg"), pathTo("test.appkg")), errorString)); + QVERIFY(!packagerCheck(PackagingJob::create(pathTo("test.appkg"), pathTo("test.appkg")), errorString)); QVERIFY2(errorString.contains(qL1S("not a directory")), qPrintable(errorString)); // no valid info.yaml - QVERIFY(!packagerCheck(Packager::create(pathTo("test.appkg"), tmp.path()), errorString)); + QVERIFY(!packagerCheck(PackagingJob::create(pathTo("test.appkg"), tmp.path()), errorString)); QVERIFY2(errorString.contains(qL1S("could not open file for reading")), qPrintable(errorString)); // add an info.yaml file createInfoYaml(tmp); // no icon - QVERIFY(!packagerCheck(Packager::create(pathTo("test.appkg"), tmp.path()), errorString)); + QVERIFY(!packagerCheck(PackagingJob::create(pathTo("test.appkg"), tmp.path()), errorString)); QVERIFY2(errorString.contains(qL1S("missing the 'icon.png' file")), qPrintable(errorString)); // add an icon createIconPng(tmp); // no valid code - QVERIFY(!packagerCheck(Packager::create(pathTo("test.appkg"), tmp.path()), errorString)); + QVERIFY(!packagerCheck(PackagingJob::create(pathTo("test.appkg"), tmp.path()), errorString)); QVERIFY2(errorString.contains(qL1S("missing the file referenced by the 'code' field")), qPrintable(errorString)); // add a code file createCode(tmp); // invalid destination - QVERIFY(!packagerCheck(Packager::create(tmp.path(), tmp.path()), errorString)); + QVERIFY(!packagerCheck(PackagingJob::create(tmp.path(), tmp.path()), errorString)); QVERIFY2(errorString.contains(qL1S("could not create package file")), qPrintable(errorString)); // now everything is correct - try again - QVERIFY2(packagerCheck(Packager::create(pathTo("test.appkg"), tmp.path()), errorString), qPrintable(errorString)); + QVERIFY2(packagerCheck(PackagingJob::create(pathTo("test.appkg"), tmp.path()), errorString), qPrintable(errorString)); // invalid source package - QVERIFY(!packagerCheck(Packager::developerSign( + QVERIFY(!packagerCheck(PackagingJob::developerSign( pathTo("no-such-file"), pathTo("test.dev-signed.appkg"), m_devCertificate, @@ -196,7 +196,7 @@ void tst_PackagerTool::test() QVERIFY2(errorString.contains(qL1S("does not exist")), qPrintable(errorString)); // invalid destination package - QVERIFY(!packagerCheck(Packager::developerSign( + QVERIFY(!packagerCheck(PackagingJob::developerSign( pathTo("test.appkg"), pathTo("."), m_devCertificate, @@ -205,7 +205,7 @@ void tst_PackagerTool::test() // invalid dev key - QVERIFY(!packagerCheck(Packager::developerSign( + QVERIFY(!packagerCheck(PackagingJob::developerSign( pathTo("test.appkg"), pathTo("test.dev-signed.appkg"), m_devCertificate, @@ -213,7 +213,7 @@ void tst_PackagerTool::test() QVERIFY2(errorString.contains(qL1S("could not create signature")), qPrintable(errorString)); // invalid store key - QVERIFY(!packagerCheck(Packager::storeSign( + QVERIFY(!packagerCheck(PackagingJob::storeSign( pathTo("test.appkg"), pathTo("test.store-signed.appkg"), m_storeCertificate, @@ -222,13 +222,13 @@ void tst_PackagerTool::test() QVERIFY2(errorString.contains(qL1S("could not create signature")), qPrintable(errorString)); // sign - QVERIFY2(packagerCheck(Packager::developerSign( + QVERIFY2(packagerCheck(PackagingJob::developerSign( pathTo("test.appkg"), pathTo("test.dev-signed.appkg"), m_devCertificate, m_devPassword), errorString), qPrintable(errorString)); - QVERIFY2(packagerCheck(Packager::storeSign( + QVERIFY2(packagerCheck(PackagingJob::storeSign( pathTo("test.appkg"), pathTo("test.store-signed.appkg"), m_storeCertificate, @@ -236,11 +236,11 @@ void tst_PackagerTool::test() hardwareId), errorString), qPrintable(errorString)); // verify - QVERIFY2(packagerCheck(Packager::developerVerify( + QVERIFY2(packagerCheck(PackagingJob::developerVerify( pathTo("test.dev-signed.appkg"), m_caFiles), errorString), qPrintable(errorString)); - QVERIFY2(packagerCheck(Packager::storeVerify( + QVERIFY2(packagerCheck(PackagingJob::storeVerify( pathTo("test.store-signed.appkg"), m_caFiles, hardwareId), errorString), qPrintable(errorString)); @@ -299,7 +299,7 @@ void tst_PackagerTool::brokenMetadata() // check if packaging actually fails with the expected error QString error; - QVERIFY(!packagerCheck(Packager::create(pathTo("test.appkg"), tmp.path()), error)); + QVERIFY(!packagerCheck(PackagingJob::create(pathTo("test.appkg"), tmp.path()), error)); AM_CHECK_ERRORSTRING(error, errorString); } |