├── plugins ├── qpmsource │ ├── qpmsource.json │ ├── qpmsource.pro │ └── qpmsourceplugin.h ├── gitsource │ ├── gitsource.json │ ├── gitsource.pro │ ├── gitsourceplugin.h │ └── gitsourceplugin.cpp └── plugins.pro ├── ci ├── prepare.sh ├── travis_init.sh ├── appveyor_deploy.bat ├── travis_test.sh └── appveyor_test.bat ├── qpmx ├── qbs │ ├── MergedStaticLibrary.qbs │ ├── qpmx.js │ └── qpmx.qbs ├── default.pri ├── qpmx.qrc ├── preparecommand.h ├── clearcachescommand.h ├── bridge.h ├── createcommand.h ├── listcommand.h ├── pluginregistry.h ├── uninstallcommand.h ├── hookcommand.h ├── publishcommand.h ├── searchcommand.h ├── initcommand.h ├── translatecommand.h ├── updatecommand.h ├── devcommand.h ├── generatecommand.h ├── bridge.cpp ├── pluginregistry.cpp ├── qbscommand.h ├── installcommand.h ├── preparecommand.cpp ├── qpmx.pro ├── clearcachescommand.cpp ├── template_static.pro ├── compilecommand.h ├── uninstallcommand.cpp ├── publishcommand.cpp ├── completitions │ ├── bash │ │ └── qpmx │ └── zsh │ │ └── _qpmx ├── listcommand.cpp ├── topsort.h ├── searchcommand.cpp ├── updatecommand.cpp ├── command.h ├── hookcommand.cpp ├── qpmxformat.h ├── createcommand.cpp ├── initcommand.cpp ├── main.cpp ├── qpmx_generated_base.pri ├── translatecommand.cpp ├── generatecommand.cpp ├── devcommand.cpp ├── qpmxformat.cpp └── installcommand.cpp ├── lib ├── libqpmx_global.h ├── libqpmx.h ├── qpmxbridge.cpp ├── qpmxbridge_p.h ├── libqpmx.cpp ├── lib.pro ├── sourceplugin.cpp ├── packageinfo.h ├── sourceplugin.h └── packageinfo.cpp ├── lib.pri ├── .gitignore ├── .qmake.conf ├── .gitmodules ├── appveyor.yml ├── .travis.yml ├── qpmx.pro ├── LICENSE └── README.md /plugins/qpmsource/qpmsource.json: -------------------------------------------------------------------------------- 1 | { 2 | "Keys": ["qpm"] 3 | } 4 | -------------------------------------------------------------------------------- /plugins/gitsource/gitsource.json: -------------------------------------------------------------------------------- 1 | { 2 | "Keys": ["git", "github"] 3 | } 4 | -------------------------------------------------------------------------------- /plugins/plugins.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | gitsource \ 5 | qpmsource 6 | -------------------------------------------------------------------------------- /ci/prepare.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | ./qtmodules-travis/ci/linux/build-docker-orig.sh 5 | ./ci/travis_test.sh 6 | -------------------------------------------------------------------------------- /qpmx/qbs/MergedStaticLibrary.qbs: -------------------------------------------------------------------------------- 1 | import qbs; 2 | 3 | StaticLibrary { 4 | type: ["qpmx-staticlibrary-merged"] 5 | 6 | Depends { name: "qpmx" } 7 | } 8 | -------------------------------------------------------------------------------- /qpmx/default.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | 3 | qpmx_static: message(configured for qpmx build) 4 | else: message(configured for source build) 5 | 6 | HEADERS += 7 | 8 | SOURCES += 9 | 10 | TRANSLATIONS += 11 | -------------------------------------------------------------------------------- /lib/libqpmx_global.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBQPMX_GLOBAL_H 2 | #define LIBQPMX_GLOBAL_H 3 | 4 | #include 5 | 6 | #if defined(QPMX_LIBRARY) 7 | # define LIBQPMX_EXPORT Q_DECL_EXPORT 8 | #else 9 | # define LIBQPMX_EXPORT Q_DECL_IMPORT 10 | #endif 11 | 12 | #endif // LIBQPMX_GLOBAL_H 13 | -------------------------------------------------------------------------------- /ci/travis_init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | currDir=$(dirname $0) 5 | 6 | if [[ "$PLATFORM" == "gcc_64" ]]; then 7 | mv ./qtmodules-travis/ci/linux/build-docker.sh ./qtmodules-travis/ci/linux/build-docker-orig.sh 8 | mv $currDir/prepare.sh ./qtmodules-travis/ci/linux/build-docker.sh 9 | fi 10 | -------------------------------------------------------------------------------- /qpmx/qpmx.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | template_static.pro 4 | qpmx_generated_base.pri 5 | default.pri 6 | qbs/qpmx.qbs 7 | qbs/qpmx.js 8 | qbs/MergedStaticLibrary.qbs 9 | 10 | 11 | -------------------------------------------------------------------------------- /lib.pri: -------------------------------------------------------------------------------- 1 | LIB_OUTDIR = $$shadowed($$PWD) 2 | 3 | win32:CONFIG(release, debug|release): LIBS += -L$$LIB_OUTDIR/lib/release/ -lqpmx 4 | else:win32:CONFIG(debug, debug|release): LIBS += -L$$LIB_OUTDIR/lib/debug/ -lqpmx 5 | else:unix: LIBS += -L$$LIB_OUTDIR/lib/ -lqpmx 6 | 7 | DEFINES += QTCOROUTINE_EXPORTED 8 | 9 | INCLUDEPATH += $$PWD/lib $$PWD/submodules/qtcoroutines/ 10 | DEPENDPATH += $$PWD/lib $$PWD/submodules/qtcoroutines/ 11 | -------------------------------------------------------------------------------- /lib/libqpmx.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBQPMX_H 2 | #define LIBQPMX_H 3 | 4 | #include 5 | 6 | #include "libqpmx_global.h" 7 | 8 | namespace qpmx { 9 | 10 | LIBQPMX_EXPORT QDir qpmxCacheDir(); 11 | 12 | LIBQPMX_EXPORT QDir srcDir(); 13 | LIBQPMX_EXPORT QDir buildDir(); 14 | LIBQPMX_EXPORT QDir tmpDir(); 15 | 16 | LIBQPMX_EXPORT QString pkgEncode(const QString &name); 17 | LIBQPMX_EXPORT QString pkgDecode(QString name); 18 | 19 | } 20 | 21 | #endif // LIBQPMX_H 22 | -------------------------------------------------------------------------------- /ci/appveyor_deploy.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | setlocal 3 | 4 | set qtplatform=%PLATFORM% 5 | for %%* in (.) do set CurrDirName=%%~nx* 6 | 7 | call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 || exit /B 1 8 | 9 | cd build-%qtplatform% 10 | nmake INSTALL_ROOT=\projects\%CurrDirName%\install deploy || exit /B 1 11 | nmake INSTALL_ROOT=\projects\%CurrDirName%\install package || exit /B 1 12 | cd .. 13 | 14 | dir /s install\ 15 | -------------------------------------------------------------------------------- /qpmx/qbs/qpmx.js: -------------------------------------------------------------------------------- 1 | 2 | function setBaseArgs(args, qpmxDir, logLevel, colors) { 3 | args.push("--dir"); 4 | args.push(qpmxDir); 5 | switch(logLevel) { 6 | case "quiet": 7 | args.push("--quiet"); 8 | break; 9 | case "warn-only": 10 | args.push("--quiet"); 11 | args.push("--verbose"); 12 | break; 13 | case "normal": 14 | break; 15 | case "verbose": 16 | args.push("--verbose"); 17 | break; 18 | } 19 | if(!colors) 20 | args.push("--no-color"); 21 | return args; 22 | } 23 | -------------------------------------------------------------------------------- /lib/qpmxbridge.cpp: -------------------------------------------------------------------------------- 1 | #include "qpmxbridge_p.h" 2 | using namespace qpmx::priv; 3 | 4 | QpmxBridge *QpmxBridge::_instance = nullptr; 5 | 6 | QpmxBridge::QpmxBridge() = default; 7 | 8 | QpmxBridge::~QpmxBridge() = default; 9 | 10 | void QpmxBridge::registerInstance(QpmxBridge *bridge) 11 | { 12 | _instance = bridge; 13 | } 14 | 15 | QpmxBridge *QpmxBridge::instance() 16 | { 17 | Q_ASSERT_X(_instance, Q_FUNC_INFO, "Cannot use libqpmx from anywhere by qpmx"); 18 | return _instance; 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | moc_*.h 24 | qrc_*.cpp 25 | ui_*.h 26 | Makefile* 27 | *build-* 28 | 29 | # QtCreator 30 | 31 | *.autosave 32 | 33 | # QtCtreator Qml 34 | *.qmlproject.user 35 | *.qmlproject.user.* 36 | 37 | # QtCtreator CMake 38 | CMakeLists.txt.user* 39 | 40 | -------------------------------------------------------------------------------- /qpmx/preparecommand.h: -------------------------------------------------------------------------------- 1 | #ifndef PREPARECOMMAND_H 2 | #define PREPARECOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | class PrepareCommand : public Command 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit PrepareCommand(QObject *parent = nullptr); 12 | 13 | QString commandName() const override; 14 | QString commandDescription() const override; 15 | QSharedPointer createCliNode() const override; 16 | 17 | protected slots: 18 | void initialize(QCliParser &parser) override; 19 | }; 20 | 21 | #endif // PREPARECOMMAND_H 22 | -------------------------------------------------------------------------------- /qpmx/clearcachescommand.h: -------------------------------------------------------------------------------- 1 | #ifndef CLEARCACHESCOMMAND_H 2 | #define CLEARCACHESCOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | class ClearCachesCommand : public Command 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit ClearCachesCommand(QObject *parent = nullptr); 12 | 13 | QString commandName() const override; 14 | QString commandDescription() const override; 15 | QSharedPointer createCliNode() const override; 16 | 17 | protected slots: 18 | void initialize(QCliParser &parser) override; 19 | }; 20 | 21 | #endif // CLEARCACHESCOMMAND_H 22 | -------------------------------------------------------------------------------- /qpmx/bridge.h: -------------------------------------------------------------------------------- 1 | #ifndef BRIDGE_H 2 | #define BRIDGE_H 3 | 4 | #include 5 | #include "command.h" 6 | 7 | namespace qpmx { 8 | namespace priv { 9 | 10 | class Bridge : public QpmxBridge 11 | { 12 | public: 13 | Bridge(Command *command); 14 | 15 | QDir qpmxCacheDir() const override; 16 | QDir srcDir() const override; 17 | QDir buildDir() const override; 18 | QDir tmpDir() const override; 19 | QString pkgEncode(const QString &name) const override; 20 | QString pkgDecode(QString name) const override; 21 | 22 | private: 23 | Command *_command; 24 | }; 25 | 26 | } 27 | } 28 | 29 | #endif // BRIDGE_H 30 | -------------------------------------------------------------------------------- /qpmx/createcommand.h: -------------------------------------------------------------------------------- 1 | #ifndef CREATECOMMAND_H 2 | #define CREATECOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | class CreateCommand : public Command 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit CreateCommand(QObject *parent = nullptr); 12 | 13 | QString commandName() const override; 14 | QString commandDescription() const override; 15 | QSharedPointer createCliNode() const override; 16 | 17 | protected slots: 18 | void initialize(QCliParser &parser) override; 19 | 20 | private: 21 | void runBaseInit(); 22 | void runPrepare(const QString &provider); 23 | }; 24 | 25 | #endif // CREATECOMMAND_H 26 | -------------------------------------------------------------------------------- /qpmx/listcommand.h: -------------------------------------------------------------------------------- 1 | #ifndef LISTCOMMAND_H 2 | #define LISTCOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | class ListCommand : public Command 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit ListCommand(QObject *parent = nullptr); 12 | 13 | QString commandName() const override; 14 | QString commandDescription() const override; 15 | QSharedPointer createCliNode() const override; 16 | 17 | protected slots: 18 | void initialize(QCliParser &parser) override; 19 | 20 | private: 21 | void listProviders(const QCliParser &parser); 22 | void listKits(const QCliParser &parser); 23 | }; 24 | 25 | #endif // LISTCOMMAND_H 26 | -------------------------------------------------------------------------------- /qpmx/pluginregistry.h: -------------------------------------------------------------------------------- 1 | #ifndef PLUGINREGISTRY_H 2 | #define PLUGINREGISTRY_H 3 | 4 | #include "sourceplugin.h" 5 | #include "qpluginfactory.h" 6 | 7 | class PluginRegistry : public QObject 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | explicit PluginRegistry(QObject *parent = nullptr); 13 | 14 | static PluginRegistry *instance(); 15 | 16 | QStringList providerNames(); 17 | qpmx::SourcePlugin *sourcePlugin(const QString &provider); 18 | 19 | void cancelAll(); 20 | 21 | private: 22 | QPluginFactory *_factory; 23 | QHash _loadCache; 24 | }; 25 | 26 | #endif // PLUGINREGISTRY_H 27 | -------------------------------------------------------------------------------- /plugins/gitsource/gitsource.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | 3 | QT += core 4 | QT -= gui 5 | CONFIG += plugin 6 | 7 | TARGET = gitsource 8 | QMAKE_TARGET_DESCRIPTION = "qpmx git Plug-In" 9 | 10 | DESTDIR = $$OUT_PWD/../qpmx 11 | 12 | HEADERS += \ 13 | gitsourceplugin.h 14 | 15 | SOURCES += \ 16 | gitsourceplugin.cpp 17 | 18 | include(../../lib.pri) 19 | 20 | DISTFILES += gitsource.json 21 | json_target.target = moc_gitsourceplugin.o 22 | json_target.depends += $$PWD/gitsource.json 23 | QMAKE_EXTRA_TARGETS += json_target 24 | 25 | include(../../submodules/deployment/install.pri) 26 | target.path = $${INSTALL_PLUGINS}/qpmx 27 | INSTALLS += target 28 | -------------------------------------------------------------------------------- /plugins/qpmsource/qpmsource.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | 3 | QT += core 4 | QT -= gui 5 | CONFIG += plugin 6 | 7 | TARGET = qpmsource 8 | QMAKE_TARGET_DESCRIPTION = "qpmx qpm Plug-In" 9 | 10 | DESTDIR = $$OUT_PWD/../qpmx 11 | 12 | HEADERS += \ 13 | qpmsourceplugin.h 14 | 15 | SOURCES += \ 16 | qpmsourceplugin.cpp 17 | 18 | include(../../lib.pri) 19 | 20 | DISTFILES += qpmsource.json 21 | json_target.target = moc_qpmsourceplugin.o 22 | json_target.depends += $$PWD/qpmsource.json 23 | QMAKE_EXTRA_TARGETS += json_target 24 | 25 | include(../../submodules/deployment/install.pri) 26 | target.path = $${INSTALL_PLUGINS}/qpmx 27 | INSTALLS += target 28 | 29 | -------------------------------------------------------------------------------- /qpmx/uninstallcommand.h: -------------------------------------------------------------------------------- 1 | #ifndef UNINSTALLCOMMAND_H 2 | #define UNINSTALLCOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | class UninstallCommand : public Command 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit UninstallCommand(QObject *parent = nullptr); 12 | 13 | QString commandName() const override; 14 | QString commandDescription() const override; 15 | QSharedPointer createCliNode() const override; 16 | 17 | protected slots: 18 | void initialize(QCliParser &parser) override; 19 | 20 | private: 21 | bool _cached; 22 | QpmxFormat _format; 23 | 24 | void removePkg(qpmx::PackageInfo package); 25 | }; 26 | 27 | #endif // UNINSTALLCOMMAND_H 28 | -------------------------------------------------------------------------------- /qpmx/hookcommand.h: -------------------------------------------------------------------------------- 1 | #ifndef HOOKCOMMAND_H 2 | #define HOOKCOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | class HookCommand : public Command 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit HookCommand(QObject *parent = nullptr); 12 | 13 | QString commandName() const override; 14 | QString commandDescription() const override; 15 | QSharedPointer createCliNode() const override; 16 | 17 | protected slots: 18 | void initialize(QCliParser &parser) override; 19 | 20 | private: 21 | void createHookSrc(const QStringList &args, QIODevice *out); 22 | void createHookCompile(const QString &inFile, QIODevice *out); 23 | }; 24 | 25 | #endif // HOOKCOMMAND_H 26 | -------------------------------------------------------------------------------- /qpmx/publishcommand.h: -------------------------------------------------------------------------------- 1 | #ifndef PUBLISHCOMMAND_H 2 | #define PUBLISHCOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | #include 7 | 8 | class PublishCommand : public Command 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | explicit PublishCommand(QObject *parent = nullptr); 14 | 15 | QString commandName() const override; 16 | QString commandDescription() const override; 17 | QSharedPointer createCliNode() const override; 18 | 19 | protected slots: 20 | void initialize(QCliParser &parser) override; 21 | 22 | private: 23 | QVersionNumber _version; 24 | QpmxFormat _format; 25 | 26 | void publishPackages(const QStringList &providers); 27 | }; 28 | 29 | #endif // PUBLISHCOMMAND_H 30 | -------------------------------------------------------------------------------- /qpmx/searchcommand.h: -------------------------------------------------------------------------------- 1 | #ifndef SEARCHCOMMAND_H 2 | #define SEARCHCOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | class SearchCommand : public Command 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit SearchCommand(QObject *parent = nullptr); 12 | 13 | QString commandName() const override; 14 | QString commandDescription() const override; 15 | QSharedPointer createCliNode() const override; 16 | 17 | protected slots: 18 | void initialize(QCliParser &parser) override; 19 | 20 | private: 21 | bool _short = false; 22 | QList> _searchResults; 23 | 24 | void performSearch(const QString &query, const QStringList &providers); 25 | void printResult(); 26 | }; 27 | 28 | #endif // SEARCHCOMMAND_H 29 | -------------------------------------------------------------------------------- /qpmx/initcommand.h: -------------------------------------------------------------------------------- 1 | #ifndef INITCOMMAND_H 2 | #define INITCOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | class InitCommand : public Command 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit InitCommand(QObject *parent = nullptr); 12 | 13 | QString commandName() const override; 14 | QString commandDescription() const override; 15 | QSharedPointer createCliNode() const override; 16 | 17 | static void prepare(const QString &proFile, bool info = false); 18 | static void tsPrepare(const QString &proFile, bool info = false); 19 | 20 | protected slots: 21 | void initialize(QCliParser &parser) override; 22 | 23 | private: 24 | void exec(const QString &step, const QStringList &arguments); 25 | }; 26 | 27 | #endif // INITCOMMAND_H 28 | -------------------------------------------------------------------------------- /lib/qpmxbridge_p.h: -------------------------------------------------------------------------------- 1 | #ifndef QPMXBRIDGE_P_H 2 | #define QPMXBRIDGE_P_H 3 | 4 | #include 5 | #include "libqpmx_global.h" 6 | 7 | namespace qpmx { 8 | namespace priv { 9 | 10 | class LIBQPMX_EXPORT QpmxBridge 11 | { 12 | public: 13 | QpmxBridge(); 14 | virtual ~QpmxBridge(); 15 | 16 | static void registerInstance(QpmxBridge *bridge); 17 | static QpmxBridge *instance(); 18 | 19 | virtual QDir qpmxCacheDir() const = 0; 20 | virtual QDir srcDir() const = 0; 21 | virtual QDir buildDir() const = 0; 22 | virtual QDir tmpDir() const = 0; 23 | virtual QString pkgEncode(const QString &name) const = 0; 24 | virtual QString pkgDecode(QString name) const = 0; 25 | 26 | private: 27 | static QpmxBridge *_instance; 28 | }; 29 | 30 | } 31 | } 32 | 33 | #endif // QPMXBRIDGE_P_H 34 | -------------------------------------------------------------------------------- /.qmake.conf: -------------------------------------------------------------------------------- 1 | VERSION = 1.6.0 2 | 3 | CONFIG += warning_clean exceptions c++14 4 | CONFIG -= app_bundle 5 | DEFINES += QT_DEPRECATED_WARNINGS QT_ASCII_CAST_WARNINGS QT_USE_QSTRINGBUILDER 6 | 7 | PROJECT_NAME = qpmx 8 | PROJECT_TARGET = qpmx 9 | QMAKE_TARGET_PRODUCT = "$$PROJECT_NAME" 10 | QMAKE_TARGET_COMPANY = "Skycoder42" 11 | QMAKE_TARGET_COPYRIGHT = "Felix Barz" 12 | QMAKE_TARGET_BUNDLE_PREFIX = de.skycoder42 13 | 14 | DEFINES += "PROJECT_NAME=\\\"$$PROJECT_NAME\\\"" 15 | DEFINES += "PROJECT_TARGET=\\\"$$PROJECT_TARGET\\\"" 16 | DEFINES += "VERSION=\\\"$$VERSION\\\"" 17 | DEFINES += "COMPANY=\"\\\"$$QMAKE_TARGET_COMPANY\\\"\"" 18 | DEFINES += "BUNDLE=\"\\\"$$QMAKE_TARGET_BUNDLE_PREFIX\\\"\"" 19 | DEFINES += APPID=\"\\\"$${QMAKE_TARGET_BUNDLE_PREFIX}.$${PROJECT_TARGET}\\\"\" 20 | 21 | android: PREFIX=/ 22 | -------------------------------------------------------------------------------- /lib/libqpmx.cpp: -------------------------------------------------------------------------------- 1 | #include "libqpmx.h" 2 | #include 3 | #include 4 | #include "qpmxbridge_p.h" 5 | 6 | QDir qpmx::qpmxCacheDir() 7 | { 8 | return qpmx::priv::QpmxBridge::instance()->qpmxCacheDir(); 9 | } 10 | 11 | QDir qpmx::srcDir() 12 | { 13 | return qpmx::priv::QpmxBridge::instance()->srcDir(); 14 | } 15 | 16 | QDir qpmx::buildDir() 17 | { 18 | return qpmx::priv::QpmxBridge::instance()->buildDir(); 19 | } 20 | 21 | QDir qpmx::tmpDir() 22 | { 23 | return qpmx::priv::QpmxBridge::instance()->tmpDir(); 24 | } 25 | 26 | QString qpmx::pkgEncode(const QString &name) 27 | { 28 | return qpmx::priv::QpmxBridge::instance()->pkgEncode(name); 29 | } 30 | 31 | QString qpmx::pkgDecode(QString name) 32 | { 33 | return qpmx::priv::QpmxBridge::instance()->pkgDecode(std::move(name)); 34 | } 35 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "submodules/qcliparser"] 2 | path = submodules/qcliparser 3 | url = https://github.com/Skycoder42/QCliParser.git 4 | [submodule "submodules/qpmx-sample-package"] 5 | path = submodules/qpmx-sample-package 6 | url = https://github.com/Skycoder42/qpmx-sample-package 7 | [submodule "submodules/qpluginfactory"] 8 | path = submodules/qpluginfactory 9 | url = https://github.com/Skycoder42/QPluginFactory.git 10 | [submodule "submodules/qtcoroutines"] 11 | path = submodules/qtcoroutines 12 | url = https://github.com/Skycoder42/QtCoroutines.git 13 | [submodule "submodules/deployment"] 14 | path = submodules/deployment 15 | url = https://github.com/Skycoder42/deployment.git 16 | [submodule "submodules/QCtrlSignals"] 17 | path = submodules/qctrlsignals 18 | url = https://github.com/Skycoder42/QCtrlSignals.git 19 | -------------------------------------------------------------------------------- /qpmx/translatecommand.h: -------------------------------------------------------------------------------- 1 | #ifndef TRANSLATECOMMAND_H 2 | #define TRANSLATECOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | class TranslateCommand : public Command 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit TranslateCommand(QObject *parent = nullptr); 12 | 13 | QString commandName() const override; 14 | QString commandDescription() const override; 15 | QSharedPointer createCliNode() const override; 16 | 17 | protected slots: 18 | void initialize(QCliParser &parser) override; 19 | 20 | private: 21 | QString _outDir; 22 | QString _qmake; 23 | QString _lconvert; 24 | QString _tsFile; 25 | QStringList _lrelease; 26 | QStringList _qpmxTsFiles; 27 | 28 | void binTranslate(); 29 | void srcTranslate(); 30 | 31 | void execute(QStringList command); 32 | QString localeString(); 33 | }; 34 | 35 | #endif // TRANSLATECOMMAND_H 36 | -------------------------------------------------------------------------------- /qpmx/updatecommand.h: -------------------------------------------------------------------------------- 1 | #ifndef UPDATECOMMAND_H 2 | #define UPDATECOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | #include 7 | 8 | class UpdateCommand : public Command 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | explicit UpdateCommand(QObject *parent = nullptr); 14 | 15 | QString commandName() const override; 16 | QString commandDescription() const override; 17 | QSharedPointer createCliNode() const override; 18 | 19 | protected slots: 20 | void initialize(QCliParser &parser) override; 21 | 22 | private: 23 | static const int LoadLimit = 5; 24 | bool _install = false; 25 | bool _skipYes = false; 26 | 27 | QList _pkgList; 28 | QList> _updateList; 29 | 30 | void checkPackages(); 31 | void checkNext(); 32 | void completeUpdate(); 33 | }; 34 | 35 | #endif // UPDATECOMMAND_H 36 | -------------------------------------------------------------------------------- /qpmx/devcommand.h: -------------------------------------------------------------------------------- 1 | #ifndef DEVCOMMAND_H 2 | #define DEVCOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | class DevCommand : public Command 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit DevCommand(QObject *parent = nullptr); 12 | 13 | QString commandName() const override; 14 | QString commandDescription() const override; 15 | QSharedPointer createCliNode() const override; 16 | 17 | protected slots: 18 | void initialize(QCliParser &parser) override; 19 | 20 | private: 21 | void addDev(const QCliParser &parser); 22 | void removeDev(const QCliParser &parser); 23 | void commitDev(const QCliParser &parser); 24 | 25 | void addAlias(const QCliParser &parser); 26 | void removeAlias(const QCliParser &parser); 27 | 28 | void runPublish(const QStringList &providers, const QpmxDevDependency &dep, const QVersionNumber &version); 29 | }; 30 | 31 | #endif // DEVCOMMAND_H 32 | -------------------------------------------------------------------------------- /qpmx/generatecommand.h: -------------------------------------------------------------------------------- 1 | #ifndef GENERATECOMMAND_H 2 | #define GENERATECOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | class GenerateCommand : public Command 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | explicit GenerateCommand(QObject *parent = nullptr); 12 | 13 | QString commandName() const override; 14 | QString commandDescription() const override; 15 | QSharedPointer createCliNode() const override; 16 | 17 | protected slots: 18 | void initialize(QCliParser &parser) override; 19 | 20 | private: 21 | QFile *_genFile; 22 | QString _qmake; 23 | 24 | BuildId kitId(const QpmxUserFormat &format) const; 25 | QpmxCacheFormat cachedFormat(const QpmxUserFormat &format) const; 26 | 27 | bool hasChanged(const QpmxUserFormat ¤tUser, const QpmxCacheFormat &cache); 28 | void createPriFile(const QpmxUserFormat ¤t); 29 | }; 30 | 31 | #endif // GENERATECOMMAND_H 32 | -------------------------------------------------------------------------------- /lib/lib.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | 3 | QT -= gui 4 | CONFIG += skip_target_version_ext 5 | 6 | TARGET = qpmx 7 | QMAKE_TARGET_DESCRIPTION = "qpmx connection library" 8 | 9 | DEFINES += QPMX_LIBRARY 10 | mac: DEFINES += _XOPEN_SOURCE 11 | 12 | QPMX_PUBLIC_HEADERS = \ 13 | libqpmx_global.h \ 14 | libqpmx.h \ 15 | packageinfo.h \ 16 | sourceplugin.h 17 | 18 | HEADERS += $$QPMX_PUBLIC_HEADERS \ 19 | qpmxbridge_p.h 20 | 21 | SOURCES += \ 22 | packageinfo.cpp \ 23 | sourceplugin.cpp \ 24 | libqpmx.cpp \ 25 | qpmxbridge.cpp 26 | 27 | CONFIG += qtcoroutines_exported 28 | include(../submodules/qtcoroutines/qtcoroutines.pri) 29 | 30 | include(../submodules/deployment/install.pri) 31 | target.path = $$INSTALL_LIBS 32 | dlltarget.path = $$INSTALL_BINS 33 | tHeaders.path = $${INSTALL_HEADERS}/qpmx 34 | tHeaders.files = $$QPMX_PUBLIC_HEADERS $$PUBLIC_HEADERS 35 | INSTALLS += target dlltarget tHeaders 36 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: 2 | - Visual Studio 2017 3 | 4 | version: build-{build} 5 | 6 | environment: 7 | QT_VER: 5.11.2 8 | EXTRA_MODULES: .skycoder42.jsonserializer 9 | QMAKE_FLAGS: "PREFIX=/deploy" 10 | NO_TESTS: true 11 | PREFIX: \ 12 | 13 | matrix: 14 | - PLATFORM: msvc2017_64 15 | # - PLATFORM: mingw53_32 16 | 17 | install: 18 | - git submodule init 19 | - git submodule update 20 | - git clone https://github.com/Skycoder42/QtModules.git .\qtmodules-travis 21 | - .\qtmodules-travis\ci\win\setup.bat 22 | 23 | build_script: 24 | - .\qtmodules-travis\ci\win\build.bat 25 | 26 | after_build: 27 | - .\ci\appveyor_test.bat 28 | - .\ci\appveyor_deploy.bat 29 | 30 | artifacts: 31 | - path: install\qpmx-*.zip 32 | 33 | deploy: 34 | provider: GitHub 35 | auth_token: 36 | secure: Cp5GRQku2ZWnKPE12NB5q11ZO0Fr5mlzdUTjnLpYJr/dki4LPVqm231edFggogy8 37 | artifact: /.*/ 38 | force_update: false 39 | on: 40 | appveyor_repo_tag: true 41 | 42 | -------------------------------------------------------------------------------- /lib/sourceplugin.cpp: -------------------------------------------------------------------------------- 1 | #include "sourceplugin.h" 2 | using namespace qpmx; 3 | 4 | SourcePlugin::SourcePlugin() = default; 5 | 6 | SourcePlugin::~SourcePlugin() = default; 7 | 8 | 9 | 10 | SourcePluginException::SourcePluginException(QByteArray errorMessage) : 11 | _message{std::move(errorMessage)} 12 | {} 13 | 14 | SourcePluginException::SourcePluginException(const QString &errorMessage) : 15 | SourcePluginException{errorMessage.toUtf8()} 16 | {} 17 | 18 | SourcePluginException::SourcePluginException(const char *errorMessage) : 19 | SourcePluginException{QByteArray{errorMessage}} 20 | {} 21 | 22 | const char *SourcePluginException::what() const noexcept 23 | { 24 | return _message.constData(); 25 | } 26 | 27 | QString SourcePluginException::qWhat() const 28 | { 29 | return QString::fromUtf8(_message); 30 | } 31 | 32 | void SourcePluginException::raise() const 33 | { 34 | throw *this; 35 | } 36 | 37 | QException *SourcePluginException::clone() const 38 | { 39 | return new SourcePluginException{_message}; 40 | } 41 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | services: 4 | - docker 5 | 6 | sudo: required 7 | dist: trusty 8 | osx_image: xcode10 9 | 10 | env: 11 | global: 12 | - QPMX_CACHE_DIR=$HOME/.qpmx-cache # needs to be set for ci scripts 13 | - QT_VER=5.11.2 14 | - DOCKER_IMAGE=common 15 | - QMAKE_FLAGS="PREFIX=/" 16 | - NO_TESTS=true 17 | - EXTRA_MODULES=".skycoder42.jsonserializer" 18 | 19 | matrix: 20 | include: 21 | - os: linux 22 | env: 23 | - PLATFORM=gcc_64 24 | - os: osx 25 | env: 26 | - PLATFORM=clang_64 27 | 28 | before_install: 29 | - git clone https://github.com/Skycoder42/QtModules.git ./qtmodules-travis 30 | - if [ "$PLATFORM" == "gcc_64" ]; then ./ci/travis_init.sh; fi 31 | - if [ "$TRAVIS_OS_NAME" == "osx" ]; then travis_wait 40 ./qtmodules-travis/ci/$TRAVIS_OS_NAME/setup.sh; else ./qtmodules-travis/ci/$TRAVIS_OS_NAME/setup.sh; fi 32 | 33 | script: 34 | - ./qtmodules-travis/ci/$TRAVIS_OS_NAME/build.sh 35 | - if [ "$PLATFORM" != "gcc_64" ]; then ./ci/travis_test.sh sudo; fi 36 | -------------------------------------------------------------------------------- /qpmx/bridge.cpp: -------------------------------------------------------------------------------- 1 | #include "bridge.h" 2 | using namespace qpmx::priv; 3 | 4 | Bridge::Bridge(Command *command) : 5 | _command{command} 6 | {} 7 | 8 | QDir Bridge::qpmxCacheDir() const 9 | { 10 | try { 11 | return _command->cacheDir(); 12 | } catch(QString &e) { 13 | qFatal("%s", qUtf8Printable(e)); 14 | } 15 | } 16 | 17 | QDir Bridge::srcDir() const 18 | { 19 | try { 20 | return _command->srcDir(); 21 | } catch(QString &e) { 22 | qFatal("%s", qUtf8Printable(e)); 23 | } 24 | } 25 | 26 | QDir Bridge::buildDir() const 27 | { 28 | try { 29 | return _command->buildDir(); 30 | } catch(QString &e) { 31 | qFatal("%s", qUtf8Printable(e)); 32 | } 33 | } 34 | 35 | QDir Bridge::tmpDir() const 36 | { 37 | try { 38 | return _command->tmpDir(); 39 | } catch(QString &e) { 40 | qFatal("%s", qUtf8Printable(e)); 41 | } 42 | } 43 | 44 | QString Bridge::pkgEncode(const QString &name) const 45 | { 46 | return Command::pkgEncode(name); 47 | } 48 | 49 | QString Bridge::pkgDecode(QString name) const 50 | { 51 | return Command::pkgDecode(std::move(name)); 52 | } 53 | -------------------------------------------------------------------------------- /qpmx.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | qpmx \ 5 | plugins \ 6 | lib 7 | 8 | qpmx.depends += lib 9 | plugins.depends += lib 10 | 11 | DISTFILES += .qmake.conf \ 12 | README.md 13 | 14 | QMAKE_EXTRA_TARGETS += lrelease 15 | 16 | CONFIG += install_log 17 | include(submodules/deployment/install.pri) 18 | 19 | win32: DEPLOY_BINS = "$$INSTALL_BINS/$${PROJECT_TARGET}.exe" 20 | !win32: CONFIG += no_deploy_qt_qm 21 | 22 | mac { 23 | qpmx_fix_bin.target = $$INSTALL_BINS/$${PROJECT_TARGET} 24 | qpmx_fix_bin.name = $${PROJECT_TARGET} 25 | qpmx_fix_bin.version = 1 26 | qpmx_fix_git.target = $$INSTALL_PLUGINS/qpmx/libgitsource.dylib 27 | qpmx_fix_git.name = $${PROJECT_TARGET} 28 | qpmx_fix_git.version = 1 29 | qpmx_fix_qpm.target = $$INSTALL_PLUGINS/qpmx/libqpmsource.dylib 30 | qpmx_fix_qpm.name = $${PROJECT_TARGET} 31 | qpmx_fix_qpm.version = 1 32 | BUNDLE_EXTRA_LIBS += qpmx_fix_bin qpmx_fix_qpm qpmx_fix_git 33 | 34 | install.commands += install_name_tool -add_rpath "@executable_path/../lib" "$(INSTALL_ROOT)$${qpmx_fix_bin.target}"$$escape_expand(\n\t) 35 | } 36 | 37 | include(submodules/deployment/deploy.pri) 38 | -------------------------------------------------------------------------------- /qpmx/pluginregistry.cpp: -------------------------------------------------------------------------------- 1 | #include "pluginregistry.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "command.h" 9 | using namespace qpmx; 10 | 11 | Q_GLOBAL_STATIC(PluginRegistry, registry) 12 | 13 | PluginRegistry::PluginRegistry(QObject *parent) : 14 | QObject{parent}, 15 | _factory{new QPluginFactory{QStringLiteral("qpmx"), this}} 16 | {} 17 | 18 | PluginRegistry *PluginRegistry::instance() 19 | { 20 | return registry; 21 | } 22 | 23 | QStringList PluginRegistry::providerNames() 24 | { 25 | return _factory->allKeys(); 26 | } 27 | 28 | SourcePlugin *PluginRegistry::sourcePlugin(const QString &provider) 29 | { 30 | try { 31 | auto srcPlg = _loadCache.value(provider, nullptr); 32 | if(srcPlg) 33 | return srcPlg; 34 | 35 | srcPlg = _factory->plugin(provider); 36 | if(!srcPlg) 37 | throw tr("No plugin found for provider: %{bld}%1%{end}").arg(provider); 38 | _loadCache.insert(provider, srcPlg); 39 | return srcPlg; 40 | } catch (QPluginLoadException &e) { 41 | throw QString::fromUtf8(e.what()); 42 | } 43 | } 44 | 45 | void PluginRegistry::cancelAll() 46 | { 47 | for(const auto &loadedPlg : qAsConst(_loadCache)) 48 | loadedPlg->cancelAll(2500); 49 | } 50 | -------------------------------------------------------------------------------- /qpmx/qbscommand.h: -------------------------------------------------------------------------------- 1 | #ifndef QBSCOMMAND_H 2 | #define QBSCOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | class QbsCommand : public Command 7 | { 8 | Q_OBJECT 9 | public: 10 | explicit QbsCommand(QObject *parent = nullptr); 11 | 12 | QString commandName() const override; 13 | QString commandDescription() const override; 14 | QSharedPointer createCliNode() const override; 15 | 16 | protected slots: 17 | void initialize(QCliParser &parser) override; 18 | 19 | private: 20 | QString _qbsPath; 21 | QDir _settingsDir; 22 | 23 | QList _pkgList; 24 | int _pkgIndex = 0; 25 | 26 | void qbsInit(const QCliParser &parser); 27 | void qbsGenerate(const QCliParser &parser); 28 | void qbsLoad(); 29 | 30 | QVersionNumber findQbsVersion(); 31 | QStringList findProfiles(const QDir &settingsDir); 32 | QString findQmake(const QDir &settingsDir, const QString &profile); 33 | 34 | void createQpmxQbs(const QDir &modRoot); 35 | void createQpmxGlobalQbs(const QDir &modRoot, const BuildId &kitId); 36 | 37 | void createNextMod(const QDir &modRoot, const BuildId &kitId); 38 | 39 | QVersionNumber readVersion(QFile &file); 40 | QString qbsPkgName(const QpmxDependency &dep); 41 | std::tuple extractHooks(const QDir &buildDir); //(hooks, qrcs) 42 | }; 43 | 44 | #endif // QBSCOMMAND_H 45 | -------------------------------------------------------------------------------- /qpmx/installcommand.h: -------------------------------------------------------------------------------- 1 | #ifndef INSTALLCOMMAND_H 2 | #define INSTALLCOMMAND_H 3 | 4 | #include "command.h" 5 | #include "qpmxformat.h" 6 | #include 7 | 8 | class InstallCommand : public Command 9 | { 10 | Q_OBJECT 11 | 12 | public: 13 | explicit InstallCommand(QObject *parent = nullptr); 14 | 15 | QString commandName() const override; 16 | QString commandDescription() const override; 17 | QSharedPointer createCliNode() const override; 18 | 19 | protected slots: 20 | void initialize(QCliParser &parser) override; 21 | 22 | private: 23 | bool _renew = false; 24 | bool _noPrepare = false; 25 | 26 | QList _pkgList; 27 | QList _aliases; 28 | int _addPkgCount = 0; 29 | 30 | void getPackages(); 31 | void completeInstall(); 32 | 33 | bool getVersion(QpmxDevDependency ¤t, qpmx::SourcePlugin *plugin, bool mustWork); 34 | 35 | bool installPackage(QpmxDevDependency ¤t, qpmx::SourcePlugin *plugin, bool mustWork); 36 | bool getSource(QpmxDevDependency ¤t, qpmx::SourcePlugin *plugin, bool mustWork, CacheLock &lock); 37 | void createSrcInclude(const QpmxDevDependency ¤t, const QpmxFormat &format, const CacheLock &lock); 38 | 39 | void verifyDeps(const QList &list, const QpmxDevDependency &base) const; 40 | void detectDeps(const QpmxFormat &format); 41 | 42 | }; 43 | 44 | #endif // INSTALLCOMMAND_H 45 | -------------------------------------------------------------------------------- /qpmx/preparecommand.cpp: -------------------------------------------------------------------------------- 1 | #include "preparecommand.h" 2 | using namespace qpmx; 3 | 4 | PrepareCommand::PrepareCommand(QObject *parent) : 5 | Command(parent) 6 | {} 7 | 8 | QString PrepareCommand::commandName() const 9 | { 10 | return QStringLiteral("prepare"); 11 | } 12 | 13 | QString PrepareCommand::commandDescription() const 14 | { 15 | return tr("Prepare a qpmx package for publishing with the given provider."); 16 | } 17 | 18 | QSharedPointer PrepareCommand::createCliNode() const 19 | { 20 | auto prepareNode = QSharedPointer::create(); 21 | prepareNode->addPositionalArgument(QStringLiteral("provider"), 22 | tr("The provider to create the publisher information for.")); 23 | return prepareNode; 24 | } 25 | 26 | void PrepareCommand::initialize(QCliParser &parser) 27 | { 28 | try { 29 | if(parser.positionalArguments().size() != 1) 30 | throw tr("You must specify exactly one provider to prepare publishing for"); 31 | 32 | auto provider = parser.positionalArguments().value(0); 33 | auto plugin = registry()->sourcePlugin(provider); 34 | if(!plugin->canPublish(provider)) 35 | throw tr("Provider %{bld}%1%{end} cannot publish packages via qpmx").arg(provider); 36 | 37 | auto format = QpmxFormat::readDefault(); 38 | format.publishers.insert(provider, plugin->createPublisherInfo(provider)); 39 | QpmxFormat::writeDefault(format); 40 | qApp->quit(); 41 | } catch(QString &s) { 42 | xCritical() << s; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Felix Barz 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /lib/packageinfo.h: -------------------------------------------------------------------------------- 1 | #ifndef PACKAGEINFO_H 2 | #define PACKAGEINFO_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include "libqpmx_global.h" 9 | 10 | namespace qpmx { 11 | 12 | class PackageInfoData; 13 | class LIBQPMX_EXPORT PackageInfo 14 | { 15 | Q_GADGET 16 | 17 | Q_PROPERTY(QString provider READ provider WRITE setProvider) 18 | Q_PROPERTY(QString package READ package WRITE setPackage) 19 | Q_PROPERTY(QVersionNumber version READ version WRITE setVersion) 20 | 21 | public: 22 | static QRegularExpression packageRegexp(); 23 | 24 | PackageInfo(QString provider = {}, 25 | const QString &package = {}, 26 | QVersionNumber version = {}); 27 | PackageInfo(const PackageInfo &other); 28 | PackageInfo(PackageInfo &&other) noexcept; 29 | PackageInfo &operator=(const PackageInfo &other); 30 | PackageInfo &operator=(PackageInfo &&other) noexcept; 31 | ~PackageInfo(); 32 | 33 | QString provider() const; 34 | QString package() const; 35 | QVersionNumber version() const; 36 | 37 | bool isComplete() const; 38 | QString toString(bool scoped = true) const; 39 | 40 | bool operator==(const PackageInfo &other) const; 41 | bool operator!=(const PackageInfo &other) const; 42 | 43 | void setProvider(QString provider); 44 | void setPackage(const QString &package); 45 | void setVersion(QVersionNumber version); 46 | 47 | private: 48 | QSharedDataPointer d; 49 | }; 50 | 51 | LIBQPMX_EXPORT uint qHash(const PackageInfo &t, uint seed); 52 | 53 | } 54 | 55 | Q_DECLARE_METATYPE(qpmx::PackageInfo) 56 | 57 | #endif // PACKAGEINFO_H 58 | -------------------------------------------------------------------------------- /plugins/gitsource/gitsourceplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef GITSOURCEPLUGIN_H 2 | #define GITSOURCEPLUGIN_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class GitSourcePlugin : public QObject, public qpmx::SourcePlugin 11 | { 12 | Q_OBJECT 13 | Q_INTERFACES(qpmx::SourcePlugin) 14 | Q_PLUGIN_METADATA(IID SourcePlugin_iid FILE "gitsource.json") 15 | 16 | public: 17 | enum ProcessMode { 18 | Invalid, 19 | LsRemote, 20 | Clone, 21 | Tag, 22 | Push 23 | }; 24 | Q_ENUM(ProcessMode) 25 | 26 | GitSourcePlugin(QObject *parent = nullptr); 27 | 28 | bool canSearch(const QString &provider) const override; 29 | bool canPublish(const QString &provider) const override; 30 | QString packageSyntax(const QString &provider) const override; 31 | bool packageValid(const qpmx::PackageInfo &package) const override; 32 | 33 | QJsonObject createPublisherInfo(const QString &provider) override; 34 | 35 | QStringList searchPackage(const QString &provider, const QString &query) override; 36 | QVersionNumber findPackageVersion(const qpmx::PackageInfo &package) override; 37 | void getPackageSource(const qpmx::PackageInfo &package, const QDir &targetDir) override; 38 | void publishPackage(const QString &provider, const QDir &qpmxDir, const QVersionNumber &version, const QJsonObject &publisherInfo) override; 39 | 40 | void cancelAll(int timeout) override; 41 | 42 | private: 43 | static QRegularExpression _githubRegex; 44 | QSet _processCache; 45 | 46 | QString pkgUrl(const qpmx::PackageInfo &package, QString *prefix = nullptr); 47 | QString pkgTag(const qpmx::PackageInfo &package); 48 | 49 | QProcess *createProcess(const QStringList &arguments, bool keepStdout = false); 50 | QString formatProcError(const QString &type, QProcess *proc); 51 | }; 52 | 53 | #endif // GITSOURCEPLUGIN_H 54 | -------------------------------------------------------------------------------- /lib/sourceplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef QPMX_SOURCEPLUGIN_H 2 | #define QPMX_SOURCEPLUGIN_H 3 | 4 | #include "packageinfo.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace qpmx { //qpmx public namespace 13 | 14 | class LIBQPMX_EXPORT SourcePlugin 15 | { 16 | Q_DISABLE_COPY(SourcePlugin) 17 | public: 18 | SourcePlugin(); 19 | virtual ~SourcePlugin(); 20 | 21 | virtual bool canSearch(const QString &provider) const = 0; 22 | virtual bool canPublish(const QString &provider) const = 0; 23 | virtual QString packageSyntax(const QString &provider) const = 0; 24 | virtual bool packageValid(const qpmx::PackageInfo &package) const = 0; 25 | 26 | virtual QJsonObject createPublisherInfo(const QString &provider) = 0; 27 | virtual QStringList searchPackage(const QString &provider, const QString &query) = 0; 28 | virtual QVersionNumber findPackageVersion(const qpmx::PackageInfo &package) = 0; 29 | virtual void getPackageSource(const qpmx::PackageInfo &package, const QDir &targetDir) = 0; 30 | virtual void publishPackage(const QString &provider, const QDir &qpmxDir, const QVersionNumber &version, const QJsonObject &publisherInfo) = 0; 31 | 32 | virtual void cancelAll(int timeout) = 0; 33 | }; 34 | 35 | class LIBQPMX_EXPORT SourcePluginException : public QException 36 | { 37 | public: 38 | SourcePluginException(QByteArray errorMessage); 39 | SourcePluginException(const QString &errorMessage); 40 | SourcePluginException(const char *errorMessage); 41 | 42 | const char *what() const noexcept override; 43 | QString qWhat() const; 44 | 45 | void raise() const override; 46 | QException *clone() const override; 47 | 48 | protected: 49 | const QByteArray _message; 50 | }; 51 | 52 | } 53 | 54 | #define SourcePlugin_iid "de.skycoder42.qpmx.SourcePlugin" 55 | Q_DECLARE_INTERFACE(qpmx::SourcePlugin, SourcePlugin_iid) 56 | 57 | #endif // QPMX_SOURCEPLUGIN_H 58 | -------------------------------------------------------------------------------- /plugins/qpmsource/qpmsourceplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef QPMSOURCEPLUGIN_H 2 | #define QPMSOURCEPLUGIN_H 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class QpmSourcePlugin : public QObject, public qpmx::SourcePlugin 11 | { 12 | Q_OBJECT 13 | Q_INTERFACES(qpmx::SourcePlugin) 14 | Q_PLUGIN_METADATA(IID SourcePlugin_iid FILE "qpmsource.json") 15 | 16 | public: 17 | enum Mode { 18 | Search, 19 | Version, 20 | Install, 21 | Publish 22 | }; 23 | Q_ENUM(Mode) 24 | 25 | QpmSourcePlugin(QObject *parent = nullptr); 26 | ~QpmSourcePlugin() override; 27 | 28 | bool canSearch(const QString &provider) const override; 29 | bool canPublish(const QString &provider) const override; 30 | QString packageSyntax(const QString &provider) const override; 31 | bool packageValid(const qpmx::PackageInfo &package) const override; 32 | 33 | QJsonObject createPublisherInfo(const QString &provider) override; 34 | 35 | QStringList searchPackage(const QString &provider, const QString &query) override; 36 | QVersionNumber findPackageVersion(const qpmx::PackageInfo &package) override; 37 | void getPackageSource(const qpmx::PackageInfo &package, const QDir &targetDir) override; 38 | void publishPackage(const QString &provider, const QDir &qpmxDir, const QVersionNumber &version, const QJsonObject &publisherInfo) override; 39 | 40 | void cancelAll(int timeout) override; 41 | 42 | private: 43 | QSet _processCache; 44 | QHash _cachedDownloads; 45 | 46 | QProcess *createProcess(const QStringList &arguments, bool keepStdout = false, bool timeout = true); 47 | QString formatProcError(const QString &type, QProcess *proc); 48 | 49 | bool completeCopyInstall(const qpmx::PackageInfo &package, QDir targetDir, QDir sourceDir); 50 | 51 | void cleanCache(const qpmx::PackageInfo &package); 52 | void cleanCaches(); 53 | void qpmTransform(const QDir &tDir); 54 | }; 55 | 56 | #endif // QPMSOURCEPLUGIN_H 57 | -------------------------------------------------------------------------------- /qpmx/qpmx.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += core jsonserializer 4 | QT -= gui 5 | 6 | CONFIG += console 7 | CONFIG -= app_bundle 8 | 9 | TARGET = qpmx 10 | QMAKE_TARGET_DESCRIPTION = "qpmx package manager" 11 | 12 | HEADERS += \ 13 | installcommand.h \ 14 | command.h \ 15 | pluginregistry.h \ 16 | qpmxformat.h \ 17 | listcommand.h \ 18 | compilecommand.h \ 19 | generatecommand.h \ 20 | topsort.h \ 21 | searchcommand.h \ 22 | uninstallcommand.h \ 23 | initcommand.h \ 24 | translatecommand.h \ 25 | devcommand.h \ 26 | preparecommand.h \ 27 | publishcommand.h \ 28 | createcommand.h \ 29 | hookcommand.h \ 30 | clearcachescommand.h \ 31 | updatecommand.h \ 32 | qbscommand.h \ 33 | bridge.h 34 | 35 | SOURCES += main.cpp \ 36 | installcommand.cpp \ 37 | command.cpp \ 38 | pluginregistry.cpp \ 39 | qpmxformat.cpp \ 40 | listcommand.cpp \ 41 | compilecommand.cpp \ 42 | generatecommand.cpp \ 43 | searchcommand.cpp \ 44 | uninstallcommand.cpp \ 45 | initcommand.cpp \ 46 | translatecommand.cpp \ 47 | devcommand.cpp \ 48 | preparecommand.cpp \ 49 | publishcommand.cpp \ 50 | createcommand.cpp \ 51 | hookcommand.cpp \ 52 | clearcachescommand.cpp \ 53 | updatecommand.cpp \ 54 | qbscommand.cpp \ 55 | bridge.cpp 56 | 57 | RESOURCES += \ 58 | qpmx.qrc 59 | 60 | DISTFILES += \ 61 | completitions/bash/qpmx \ 62 | qbs/module.qbs \ 63 | qbs/dep-base.qbs \ 64 | qbs/MergedStaticLibrary.qbs 65 | 66 | include(../submodules/qcliparser/qcliparser.pri) 67 | include(../submodules/qpluginfactory/qpluginfactory.pri) 68 | include(../submodules/qctrlsignals/qctrlsignals.pri) 69 | include(../lib.pri) 70 | 71 | include(../submodules/deployment/install.pri) 72 | target.path = $$INSTALL_BINS 73 | INSTALLS += target 74 | 75 | unix { 76 | bashcomp.path = $${INSTALL_SHARE}/bash-completion/completions/ 77 | bashcomp.files = completitions/bash/qpmx 78 | zshcomp.path = $${INSTALL_SHARE}/zsh/site-functions/ 79 | zshcomp.files = completitions/zsh/_qpmx 80 | INSTALLS += bashcomp zshcomp 81 | } 82 | -------------------------------------------------------------------------------- /qpmx/clearcachescommand.cpp: -------------------------------------------------------------------------------- 1 | #include "clearcachescommand.h" 2 | using namespace qpmx; 3 | 4 | ClearCachesCommand::ClearCachesCommand(QObject *parent) : 5 | Command(parent) 6 | {} 7 | 8 | QString ClearCachesCommand::commandName() const 9 | { 10 | return QStringLiteral("clean-caches"); 11 | } 12 | 13 | QString ClearCachesCommand::commandDescription() const 14 | { 15 | return tr("Removes cached sources, binaries and temporary files."); 16 | } 17 | 18 | QSharedPointer ClearCachesCommand::createCliNode() const 19 | { 20 | auto cleanNode = QSharedPointer::create(); 21 | cleanNode->addOption({ 22 | QStringLiteral("no-src"), 23 | tr("Keep cached sources, do not delete them, only build files.") 24 | }); 25 | cleanNode->addOption({ 26 | {QStringLiteral("y"), QStringLiteral("yes")}, 27 | tr("Skip the prompt for running qpmx instances.") 28 | }); 29 | return cleanNode; 30 | } 31 | 32 | void ClearCachesCommand::initialize(QCliParser &parser) 33 | { 34 | try { 35 | if(!parser.isSet(QStringLiteral("yes"))) { 36 | QFile console; 37 | if(!console.open(stdin, QIODevice::ReadOnly | QIODevice::Text)) 38 | throw tr("Failed to access console with error: %1").arg(console.errorString()); 39 | xWarning() << tr("Make shure no other qpmx instances are running as cleaning caches would corrupt them!"); 40 | console.readLine(); 41 | } 42 | 43 | if(!tmpDir().removeRecursively()) 44 | xWarning() << tr("Failed to completly remove temporary files"); 45 | else 46 | xDebug() << tr("Removed temporary files"); 47 | if(!buildDir().removeRecursively()) 48 | xWarning() << tr("Failed to completly remove cached build files"); 49 | else 50 | xDebug() << tr("Removed cached build files"); 51 | if(!parser.isSet(QStringLiteral("no-src"))) { 52 | if(!srcDir().removeRecursively()) 53 | xWarning() << tr("Failed to completly remove cached sources files"); 54 | else 55 | xDebug() << tr("Removed cached source files"); 56 | } 57 | qApp->quit(); 58 | } catch (QString &s) { 59 | xCritical() << s; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ci/travis_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # $1 sudo 3 | set -e 4 | 5 | SUDO=$1 6 | unset QPMX_CACHE_DIR 7 | 8 | if [[ "$PLATFORM" == "clang_64" ]]; then 9 | export MAKEFLAGS="-j$(sysctl -n hw.ncpu)" 10 | sudo mkdir -p /private || true 11 | sudo ln -s /tmp /private/tmp || true 12 | sudo ln -s /Users /private/Users || true 13 | else 14 | export MAKEFLAGS="-j$(nproc)" 15 | fi 16 | 17 | export PATH="$PWD/install/bin:$PATH" 18 | export QT_PLUGIN_PATH="$PWD/install/plugins:$QT_PLUGIN_PATH" 19 | export LD_LIBRARY_PATH="$PWD/install/lib:$LD_LIBRARY_PATH" 20 | which qpmx 21 | if [[ "$(which qpmx)" != "$PWD/install/bin/qpmx" ]]; then 22 | echo wrong qpmx executable found 23 | exit 1 24 | fi 25 | qpmx list providers 26 | 27 | # build tests (bin and src) 28 | for i in 0 1 2 3; do #compile, compile-dev, src-dev, src 29 | if [[ "$i" == "1" ]]; then 30 | mv submodules/qpmx-sample-package/qpmx-test/qpmx.json.user.cm submodules/qpmx-sample-package/qpmx-test/qpmx.json.user 31 | fi 32 | if [[ "$i" == "2" ]]; then 33 | rm submodules/qpmx-sample-package/qpmx-test/qpmx.json 34 | mv submodules/qpmx-sample-package/qpmx-test/qpmx.json.src submodules/qpmx-sample-package/qpmx-test/qpmx.json 35 | fi 36 | if [[ "$i" == "3" ]]; then 37 | rm submodules/qpmx-sample-package/qpmx-test/qpmx.json.user 38 | fi 39 | 40 | for j in 0 1 2; do 41 | echo running test case $i-$j 42 | 43 | M_FLAGS="$QMAKE_FLAGS" 44 | if [[ "$j" == "1" ]]; then 45 | M_FLAGS="$QMAKE_FLAGS CONFIG+=test_as_shared" 46 | fi 47 | if [[ "$j" == "2" ]]; then 48 | M_FLAGS="$QMAKE_FLAGS CONFIG+=test_as_static" 49 | fi 50 | 51 | mkdir build-$PLATFORM/tests-$i-$j 52 | pushd build-$PLATFORM/tests-$i-$j 53 | 54 | /opt/qt/$QT_VER/$PLATFORM/bin/qmake $M_FLAGS ../../submodules/qpmx-sample-package/qpmx-test/ 55 | make qmake_all 56 | make 57 | make lrelease 58 | make INSTALL_ROOT=$(mktemp -d) install 59 | 60 | if [[ "$j" == "0" ]]; then 61 | QT_QPA_PLATFORM=minimal ./test 62 | fi 63 | popd 64 | done 65 | done 66 | 67 | #extra tests 68 | #test install without provider/version 69 | qpmx install -cr --verbose de.skycoder42.qpathedit https://github.com/Skycoder42/qpmx-sample-package.git 70 | -------------------------------------------------------------------------------- /qpmx/template_static.pro: -------------------------------------------------------------------------------- 1 | # Defined in .qmake.conf: QPMX_* 2 | TEMPLATE = lib 3 | CONFIG += staticlib 4 | win32: CONFIG += debug_and_release 5 | else: CONFIG += release 6 | 7 | # add modules (from qtbase, qtdeclarative, but only if available) 8 | QT = 9 | QT_WANTS *= core gui widgets network xml sql concurrent dbus qml quick quickwidgets particles 10 | for(mod, QT_WANTS):qtHaveModule($$mod): QT *= $$mod 11 | 12 | TARGET = $$qtLibraryTarget($$QPMX_TARGET) 13 | VERSION = $$QPMX_VERSION 14 | 15 | CONFIG += qpmx_static 16 | include($$QPMX_PRI_INCLUDE) 17 | 18 | # startup hook (i know, it's ugly...) 19 | REAL_SOURCES = $$SOURCES 20 | SOURCES = 21 | hook_compiler.name = hook ${QMAKE_FILE_IN} 22 | hook_compiler.input = REAL_SOURCES 23 | hook_compiler.variable_out = SOURCES 24 | hook_compiler.commands = $$QPMX_BIN hook --prepare ${QMAKE_FILE_IN} --out ${QMAKE_FILE_OUT} 25 | hook_compiler.output = $$OUT_PWD/.srccache/${QMAKE_FILE_BASE}${QMAKE_FILE_EXT} 26 | QMAKE_EXTRA_COMPILERS += hook_compiler 27 | 28 | # resources 29 | !isEmpty(RESOURCES): write_file($$OUT_PWD/.qpmx_resources, RESOURCES) 30 | 31 | # install stuff 32 | isEmpty(PUBLIC_HEADERS): PUBLIC_HEADERS = $$HEADERS 33 | 34 | target.path = $$QPMX_INSTALL/lib 35 | headers.files = $$PUBLIC_HEADERS 36 | headers.path = $$QPMX_INSTALL/include 37 | 38 | INSTALLS += headers 39 | isEmpty(REAL_SOURCES):isEmpty(GENERATED_SOURCES):write_file($$OUT_PWD/.no_sources_detected) 40 | else: INSTALLS += target 41 | 42 | installall.target = all-install 43 | win32 { 44 | CONFIG += debug_and_release 45 | installall.depends += release-install debug-install 46 | } else: installall.depends += install 47 | QMAKE_EXTRA_TARGETS += installall 48 | 49 | #translations 50 | isEmpty(LRELEASE) { 51 | qtPrepareTool(LRELEASE, lrelease) 52 | LRELEASE += -nounfinished 53 | } 54 | 55 | lrelease_compiler.name = lrelease ${QMAKE_FILE_IN} 56 | lrelease_compiler.input = TRANSLATIONS 57 | lrelease_compiler.variable_out = TRANSLATIONS_QM 58 | lrelease_compiler.commands = $$LRELEASE ${QMAKE_FILE_IN} -qm ${QMAKE_FILE_OUT} 59 | lrelease_compiler.output = $$OUT_PWD/${QMAKE_FILE_BASE}.qm 60 | lrelease_compiler.CONFIG += no_link target_predeps 61 | 62 | QMAKE_EXTRA_COMPILERS += lrelease_compiler 63 | 64 | ts_install.path = $$QPMX_INSTALL/translations 65 | ts_install.CONFIG += no_check_exist 66 | #ts_install.files = $$TRANSLATIONS_QM 67 | for(tsfile, TRANSLATIONS) { 68 | tsBase = $$basename(tsfile) 69 | ts_install.files += "$$OUT_PWD/$$replace(tsBase, \.ts, .qm)" 70 | } 71 | INSTALLS += ts_install 72 | -------------------------------------------------------------------------------- /qpmx/compilecommand.h: -------------------------------------------------------------------------------- 1 | #ifndef COMPILECOMMAND_H 2 | #define COMPILECOMMAND_H 3 | 4 | #include "command.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class QtKitInfo 11 | { 12 | public: 13 | QtKitInfo(QString path = {}); 14 | 15 | static QUuid findKitId(const QDir &buildDir, const QString &qmake); 16 | static QList readFromSettings(const QDir &buildDir); 17 | static void writeToSettings(const QDir &buildDir, const QList &kitInfos); 18 | 19 | operator bool() const; 20 | bool operator ==(const QtKitInfo &other) const; 21 | 22 | QUuid id; 23 | QString path; 24 | 25 | QVersionNumber qmakeVer; 26 | QVersionNumber qtVer; 27 | QString spec; 28 | QString xspec; 29 | QString hostPrefix; 30 | QString installPrefix; 31 | QString sysRoot; 32 | }; 33 | 34 | class BuildDir 35 | { 36 | public: 37 | BuildDir(); 38 | BuildDir(const QDir &buildDir); 39 | 40 | bool isValid() const; 41 | void setAutoRemove(bool b); 42 | QString path() const; 43 | QString filePath(const QString &fileName) const; 44 | 45 | private: 46 | QTemporaryDir _tDir; 47 | QDir _pDir; 48 | }; 49 | 50 | class CompileCommand : public Command 51 | { 52 | Q_OBJECT 53 | 54 | public: 55 | enum Stage { 56 | None, 57 | QMake, 58 | Make, 59 | Source, 60 | PriGen 61 | }; 62 | Q_ENUM(Stage) 63 | 64 | explicit CompileCommand(QObject *parent = nullptr); 65 | 66 | QString commandName() const override; 67 | QString commandDescription() const override; 68 | QSharedPointer createCliNode() const override; 69 | 70 | protected slots: 71 | void initialize(QCliParser &parser) override; 72 | void finalize() override; 73 | 74 | private: 75 | bool _recompile = false; 76 | bool _fwdStderr = false; 77 | bool _clean = false; 78 | 79 | QList _pkgList; 80 | QList _explicitPkg; 81 | QList _aliases; 82 | QList _qtKits; 83 | #ifndef QPMX_NO_MAKEBUG 84 | QProcessEnvironment _procEnv; 85 | #endif 86 | 87 | // temporary vars for compile steps 88 | QpmxDevDependency _current; 89 | QtKitInfo _kit; 90 | QScopedPointer _compileDir; 91 | QpmxFormat _format; 92 | QProcess *_process = nullptr; 93 | bool _hasBinary = true; 94 | 95 | void compilePackages(); 96 | void qmake(); 97 | void make(); 98 | void install(); 99 | void priGen(); 100 | 101 | void depCollect(); 102 | QString findMake(); 103 | QStringList readMultiVar(const QString &dirName, bool recursive = false); 104 | QStringList readVar(const QString &fileName); 105 | void initProcess(const QString &program, const QString &logBase); 106 | Q_NORETURN void raiseError(const QString &logBase); 107 | #ifndef QPMX_NO_MAKEBUG 108 | void setupEnv(); 109 | #endif 110 | 111 | void initKits(const QStringList &qmakes); 112 | QtKitInfo createKit(const QString &qmakePath); 113 | QtKitInfo updateKit(QtKitInfo oldKit, bool mustWork); 114 | }; 115 | 116 | #endif // COMPILECOMMAND_H 117 | -------------------------------------------------------------------------------- /qpmx/uninstallcommand.cpp: -------------------------------------------------------------------------------- 1 | #include "uninstallcommand.h" 2 | using namespace qpmx; 3 | 4 | UninstallCommand::UninstallCommand(QObject *parent) : 5 | Command(parent), 6 | _cached(false), 7 | _format() 8 | {} 9 | 10 | QString UninstallCommand::commandName() const 11 | { 12 | return QStringLiteral("uninstall"); 13 | } 14 | 15 | QString UninstallCommand::commandDescription() const 16 | { 17 | return tr("Uninstall a qpmx package from the current project."); 18 | } 19 | 20 | QSharedPointer UninstallCommand::createCliNode() const 21 | { 22 | auto uninstallNode = QSharedPointer::create(); 23 | uninstallNode->addOption({ 24 | {QStringLiteral("c"), QStringLiteral("cached")}, 25 | tr("Not only remove the dependency from the qpmx.json, but remove all cached sources and compiled libraries.") 26 | }); 27 | uninstallNode->addPositionalArgument(QStringLiteral("packages"), 28 | tr("The packages to remove from the qpmx.json."), 29 | QStringLiteral("[::][@] ...")); 30 | return uninstallNode; 31 | } 32 | 33 | void UninstallCommand::initialize(QCliParser &parser) 34 | { 35 | try { 36 | _cached = parser.isSet(QStringLiteral("cached")); 37 | 38 | if(parser.positionalArguments().isEmpty()) 39 | throw tr("You must specify at least 1 package to remove."); 40 | 41 | xDebug() << tr("Uninstalling %n package(s) from the command line", "", parser.positionalArguments().size()); 42 | auto pkgList = readCliPackages(parser.positionalArguments()); 43 | 44 | _format = QpmxFormat::readDefault(); 45 | for(const auto &pkg : pkgList) 46 | removePkg(pkg); 47 | QpmxFormat::writeDefault(_format); 48 | 49 | xDebug() << tr("Package uninstallation completed"); 50 | qApp->quit(); 51 | } catch(QString &s) { 52 | xCritical() << s; 53 | } 54 | } 55 | 56 | void UninstallCommand::removePkg(PackageInfo package) 57 | { 58 | auto found = false; 59 | if(package.provider().isNull()) { 60 | for(const auto &dep : qAsConst(_format.dependencies)) { 61 | if(dep.package == package.package() && 62 | (package.version().isNull() || 63 | package.version() == dep.version)) { 64 | package = dep.pkg(); 65 | found = true; 66 | } 67 | } 68 | 69 | if(!found) 70 | throw tr("Failed to find a full package in qpmx.json that matches %1").arg(package.toString()); 71 | } else if(package.version().isNull()) { 72 | for(const auto &dep : qAsConst(_format.dependencies)) { 73 | if(dep.package == package.package() && 74 | dep.provider == package.provider()) { 75 | package = dep.pkg(); 76 | found = true; 77 | } 78 | } 79 | 80 | if(!found) 81 | throw tr("Failed to find a full package in qpmx.json that matches %1").arg(package.toString()); 82 | } 83 | 84 | if(!_format.dependencies.removeOne(package)) 85 | xWarning() << tr("Package %1 not found in qpmx.json").arg(package.toString()); 86 | else 87 | xDebug() << tr("Removed package %1 from qpmx.json").arg(package.toString()); 88 | 89 | if(_cached) { 90 | auto _pl = pkgLock(package); 91 | cleanCaches(package, _pl); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /qpmx/publishcommand.cpp: -------------------------------------------------------------------------------- 1 | #include "publishcommand.h" 2 | using namespace qpmx; 3 | 4 | PublishCommand::PublishCommand(QObject *parent) : 5 | Command{parent} 6 | {} 7 | 8 | QString PublishCommand::commandName() const 9 | { 10 | return QStringLiteral("publish"); 11 | } 12 | 13 | QString PublishCommand::commandDescription() const 14 | { 15 | return tr("Publish a new version of a qpmx package for one or more providers."); 16 | } 17 | 18 | QSharedPointer PublishCommand::createCliNode() const 19 | { 20 | auto publishNode = QSharedPointer::create(); 21 | publishNode->addOption({ 22 | {QStringLiteral("p"), QStringLiteral("provider")}, 23 | tr("Pass the to push to. Can be specified multiple times. If not specified, " 24 | "the package is published for all providers prepared for the package."), 25 | tr("provider") 26 | }); 27 | publishNode->addPositionalArgument(QStringLiteral("version"), 28 | tr("The new package version to publish.")); 29 | return publishNode; 30 | } 31 | 32 | void PublishCommand::initialize(QCliParser &parser) 33 | { 34 | try { 35 | if(parser.positionalArguments().size() != 1) 36 | throw tr("You must specify the version to publish as a single parameter"); 37 | 38 | _version = QVersionNumber::fromString(parser.positionalArguments().value(0)); 39 | if(_version.isNull()) 40 | throw tr("Invalid version! Please pass a valid version name"); 41 | _format = QpmxFormat::readDefault(true); 42 | 43 | xInfo() << tr("Publishing package for version %1").arg(_version.toString()); 44 | 45 | QStringList providers; 46 | providers.append(parser.values(QStringLiteral("provider"))); 47 | if(providers.isEmpty()) { 48 | providers.append(_format.publishers.keys()); 49 | if(providers.isEmpty()) { 50 | throw tr("Unable to publish package without any providers. " 51 | "Run qpmx prepare to prepare a package for publishing"); 52 | } 53 | } 54 | 55 | publishPackages(providers); 56 | } catch (QString &s) { 57 | xCritical() << s; 58 | } 59 | } 60 | 61 | void PublishCommand::publishPackages(const QStringList &providers) 62 | { 63 | for(const auto &provider : providers) { 64 | auto plugin = registry()->sourcePlugin(provider); 65 | 66 | if(!plugin->canPublish(provider)) 67 | throw tr("Provider %{bld}%1%{end} cannot publish packages via qpmx!").arg(provider); 68 | if(!_format.publishers.contains(provider)) { 69 | throw tr("Package has not been prepare for provider %{bld}%1%{end}. " 70 | "Run the following command to prepare it: qpmx prepare %1") 71 | .arg(provider); 72 | } 73 | 74 | try { 75 | xInfo() << tr("Publishing for provider %{bld}%1%{end}").arg(provider); 76 | plugin->publishPackage(provider, QDir::current(), _version, _format.publishers.value(provider)); 77 | } catch (qpmx::SourcePluginException &e) { 78 | throw tr("Failed to publish package for provider %{bld}%1%{end} with error: %2") 79 | .arg(provider, e.qWhat()); 80 | } 81 | } 82 | 83 | xDebug() << tr("Package publishing completed"); 84 | qApp->quit(); 85 | } 86 | -------------------------------------------------------------------------------- /qpmx/completitions/bash/qpmx: -------------------------------------------------------------------------------- 1 | # file: qpmx 2 | # qpmx parameter-completion 3 | 4 | function _qpm_contains_element { 5 | local e match="$1" 6 | shift 7 | for e; do [[ "$e" == "$match" ]] && return 0; done 8 | return 1 9 | } 10 | 11 | function _qpmx { 12 | local cur prev last bin arg optargs prefix 13 | 14 | cur=${COMP_WORDS[COMP_CWORD]} 15 | bin=${COMP_WORDS[0]} 16 | last=${COMP_WORDS[COMP_CWORD-1]} 17 | prev=("${COMP_WORDS[@]:1}") 18 | unset 'prev[${#prev[@]}-1]' 19 | COMPREPLY=() 20 | 21 | ## special last arg completitions for options 22 | case "$last" in 23 | -d|--dir|--dev-cache) 24 | COMPREPLY=($(compgen -o plusdirs -d -- "${COMP_WORDS[COMP_CWORD]}")) 25 | ;; 26 | -m|--qmake|--qpmx-prepare|--ts-prepare) 27 | COMPREPLY=($(compgen -o plusdirs -f -- "${COMP_WORDS[COMP_CWORD]}")) 28 | ;; 29 | -p|--prepare|--provider) 30 | COMPREPLY=($(compgen -W "$($bin list providers --short)" -- $cur)) 31 | ;; 32 | *) ##default: normal completition 33 | optargs='-h --help -v --version --verbose -q --quiet --no-color -d --dir --dev-cache' 34 | prefix='clean-caches compile create dev generate init install list prepare publish qbs search uninstall update' 35 | for arg in "${prev[@]}"; do 36 | ## collect all opt args 37 | case "$arg" in 38 | clean-caches) 39 | optargs="$optargs --no-src -y --yes" 40 | ;; 41 | compile) 42 | optargs="$optargs -m --qmake -g --global -r --recompile -e --stderr -c --clean" 43 | ;; 44 | create) 45 | optargs="$optargs -p --prepare" 46 | ;; 47 | commit) 48 | optargs="$optargs --no-add -p --provider" 49 | ;; 50 | generate) 51 | optargs="$optargs -m --qmake -r --recreate -p --profile --qbs-version" 52 | ;; 53 | init) 54 | optargs="$optargs -r -e --stderr -c --clean --qpmx-prepare --ts-prepare -p --profile --qbs-version" 55 | ;; 56 | install) 57 | optargs="$optargs -r --renew -c --cache --no-prepare" 58 | ;; 59 | list) 60 | optargs="$optargs --short" 61 | ;; 62 | publish) 63 | optargs="$optargs -p --provider" 64 | ;; 65 | remove) 66 | optargs="$optargs --alias" 67 | ;; 68 | search) 69 | optargs="$optargs -p --provider --short" 70 | ;; 71 | uninstall) 72 | optargs="$optargs -c --cached" 73 | ;; 74 | update) 75 | optargs="$optargs -i --install -y --yes" 76 | ;; 77 | esac 78 | 79 | ## find the prefix: check if prefix was in prev list 80 | if _qpm_contains_element $arg $prefix; then 81 | case "$arg" in 82 | dev) 83 | prefix="add alias commit remove" 84 | ;; 85 | list) 86 | prefix="kits providers" 87 | ;; 88 | qbs) 89 | prefix="init generate load" 90 | ;; 91 | *) 92 | prefix="" 93 | ;; 94 | esac 95 | fi 96 | done 97 | COMPREPLY=($(compgen -W "$prefix $optargs" -- $cur)) 98 | ;; 99 | esac 100 | 101 | 102 | return 0 103 | } 104 | 105 | complete -F _qpmx -o filenames qpmx 106 | -------------------------------------------------------------------------------- /lib/packageinfo.cpp: -------------------------------------------------------------------------------- 1 | #include "packageinfo.h" 2 | using namespace qpmx; 3 | 4 | namespace qpmx { 5 | 6 | class PackageInfoData : public QSharedData 7 | { 8 | public: 9 | QString provider; 10 | QString package; 11 | QVersionNumber version; 12 | 13 | PackageInfoData(QString provider, const QString &package, QVersionNumber version) : 14 | QSharedData(), 15 | provider(std::move(provider)), 16 | package(package.toLower()), 17 | version(std::move(version)) 18 | {} 19 | 20 | PackageInfoData(const PackageInfoData &other) = default; 21 | PackageInfoData(PackageInfoData &&other) = default; 22 | 23 | bool operator==(const PackageInfoData &other) const 24 | { 25 | return provider == other.provider && 26 | package == other.package && 27 | version == other.version; 28 | } 29 | }; 30 | 31 | } 32 | 33 | QRegularExpression PackageInfo::packageRegexp() { 34 | return QRegularExpression{ 35 | QStringLiteral(R"__(^(?:([^:]*)::)?(.*?)(?:@([\w\.-]*))?$)__"), 36 | QRegularExpression::CaseInsensitiveOption 37 | }; 38 | } 39 | 40 | PackageInfo::PackageInfo(QString provider, const QString &package, QVersionNumber version) : 41 | d{new PackageInfoData{std::move(provider), package, std::move(version)}} 42 | {} 43 | 44 | PackageInfo::~PackageInfo() = default; 45 | 46 | PackageInfo::PackageInfo(const PackageInfo &other) = default; 47 | 48 | PackageInfo::PackageInfo(PackageInfo &&other) noexcept = default; 49 | 50 | PackageInfo &PackageInfo::operator=(const PackageInfo &other) = default; 51 | 52 | PackageInfo &PackageInfo::operator=(PackageInfo &&other) noexcept = default; 53 | 54 | QString PackageInfo::provider() const 55 | { 56 | return d->provider; 57 | } 58 | 59 | QString PackageInfo::package() const 60 | { 61 | return d->package; 62 | } 63 | 64 | QVersionNumber PackageInfo::version() const 65 | { 66 | return d->version; 67 | } 68 | 69 | bool PackageInfo::isComplete() const 70 | { 71 | return !d->provider.isEmpty() && 72 | !d->package.isEmpty() && 73 | !d->version.isNull(); 74 | } 75 | 76 | QString PackageInfo::toString(bool scoped) const 77 | { 78 | auto res = d->package; 79 | if(!d->provider.isNull()) 80 | res.prepend(d->provider + QStringLiteral("::")); 81 | if(!d->version.isNull()) 82 | res.append(QLatin1Char('@') + d->version.toString()); 83 | if(scoped) 84 | return QStringLiteral("%{pkg}") + res + QStringLiteral("%{end}"); 85 | else 86 | return res; 87 | } 88 | 89 | bool PackageInfo::operator==(const PackageInfo &other) const 90 | { 91 | return d == other.d || 92 | *d == *other.d; 93 | } 94 | 95 | bool PackageInfo::operator!=(const PackageInfo &other) const 96 | { 97 | return !operator ==(other); 98 | } 99 | 100 | void PackageInfo::setProvider(QString provider) 101 | { 102 | d->provider = std::move(provider); 103 | } 104 | 105 | void PackageInfo::setPackage(const QString &package) 106 | { 107 | d->package = package.toLower(); 108 | } 109 | 110 | void PackageInfo::setVersion(QVersionNumber version) 111 | { 112 | d->version = std::move(version); 113 | } 114 | 115 | uint qpmx::qHash(const PackageInfo &t, uint seed) 116 | { 117 | return qHash(t.provider(), seed) ^ 118 | qHash(t.package(), seed) ^ 119 | qHash(t.version(), seed); 120 | } 121 | -------------------------------------------------------------------------------- /qpmx/listcommand.cpp: -------------------------------------------------------------------------------- 1 | #include "listcommand.h" 2 | #include "compilecommand.h" 3 | 4 | #include 5 | using namespace qpmx; 6 | 7 | #define print(x) std::cout << QString(x).toStdString() << std::endl 8 | 9 | ListCommand::ListCommand(QObject *parent) : 10 | Command(parent) 11 | {} 12 | 13 | QString ListCommand::commandName() const 14 | { 15 | return QStringLiteral("list"); 16 | } 17 | 18 | QString ListCommand::commandDescription() const 19 | { 20 | return tr("List things like providers, qmake versions and other components of qpmx."); 21 | } 22 | 23 | QSharedPointer ListCommand::createCliNode() const 24 | { 25 | auto listNode = QSharedPointer::create(); 26 | listNode->addOption({ 27 | QStringLiteral("short"), 28 | QStringLiteral("Only list provider/qmake names, no other details.") 29 | }); 30 | listNode->addLeafNode(QStringLiteral("providers"), 31 | tr("List the package provider backends that are available for qpmx.")); 32 | listNode->addLeafNode(QStringLiteral("kits"), 33 | tr("List all currently known Qt kits and the corresponding qmake executable.")); 34 | 35 | return listNode; 36 | } 37 | 38 | void ListCommand::initialize(QCliParser &parser) 39 | { 40 | try { 41 | if(parser.enterContext(QStringLiteral("providers"))) 42 | listProviders(parser); 43 | else if(parser.enterContext(QStringLiteral("kits"))) 44 | listKits(parser); 45 | else 46 | Q_UNREACHABLE(); 47 | qApp->quit(); 48 | } catch (QString &s) { 49 | xCritical() << s; 50 | } 51 | parser.leaveContext(); 52 | } 53 | 54 | void ListCommand::listProviders(const QCliParser &parser) 55 | { 56 | if(parser.isSet(QStringLiteral("short"))) 57 | print(registry()->providerNames().join(QLatin1Char(' '))); 58 | else { 59 | const auto providers = registry()->providerNames(); 60 | QList rows; 61 | rows.reserve(providers.size()); 62 | for(const auto &provider : providers) { 63 | auto plugin = registry()->sourcePlugin(provider); 64 | rows.append({ 65 | provider, 66 | plugin->canSearch(provider) ? tr("yes") : tr("no"), 67 | plugin->canPublish(provider) ? tr("yes") : tr("no"), 68 | plugin->packageSyntax(provider) 69 | }); 70 | } 71 | 72 | printTable({tr("Provider"), tr("Can Search"), tr("Can Publish"), tr("Package Syntax")}, 73 | {10, 5, 5, 0}, 74 | rows); 75 | } 76 | } 77 | 78 | void ListCommand::listKits(const QCliParser &parser) 79 | { 80 | auto kits = QtKitInfo::readFromSettings(buildDir()); 81 | 82 | if(parser.isSet(QStringLiteral("short"))) { 83 | QStringList paths; 84 | paths.reserve(kits.size()); 85 | for(const auto &kit : kits) 86 | paths.append(kit.path); 87 | print(paths.join(QLatin1Char(' '))); 88 | } else { 89 | QList rows; 90 | rows.reserve(kits.size()); 91 | for(const auto &kit : kits) { 92 | rows.append({ 93 | kit.qtVer.toString(), 94 | kit.qmakeVer.toString(), 95 | kit.xspec, 96 | kit.id.toString(), 97 | kit.path 98 | }); 99 | } 100 | 101 | printTable({tr("Qt"), tr("qmake"), tr("xspec"), tr("ID"), tr("qmake path")}, 102 | {6, 5, 15, 38, 0}, 103 | rows); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /qpmx/topsort.h: -------------------------------------------------------------------------------- 1 | #ifndef TOPSORT_H 2 | #define TOPSORT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | template 11 | class TopSort 12 | { 13 | public: 14 | TopSort(); 15 | TopSort(const QList &list, const std::function &comparator = {}); 16 | 17 | void addData(const T &data); 18 | bool contains(const T &data) const; 19 | 20 | void addDependency(int from, int to); 21 | void addDependency(const T &from, const T &to); 22 | 23 | QList sort() const; 24 | 25 | private: 26 | const std::function _comparator; 27 | QList _data; 28 | QMultiHash _dependencies; 29 | 30 | int indexOf(const T &data) const; 31 | }; 32 | 33 | // ------------- Implementation ------------- 34 | 35 | template 36 | TopSort::TopSort() : 37 | _data(), 38 | _dependencies() 39 | {} 40 | 41 | template 42 | TopSort::TopSort(const QList &list, const std::function &comparator) : 43 | _comparator(comparator ? comparator : [](const T &a, const T &b){ return a == b; }), 44 | _data(list), 45 | _dependencies() 46 | {} 47 | 48 | template 49 | void TopSort::addData(const T &data) 50 | { 51 | _data.append(data); 52 | } 53 | 54 | template 55 | bool TopSort::contains(const T &data) const 56 | { 57 | return indexOf(data) != -1; 58 | } 59 | 60 | template 61 | void TopSort::addDependency(int from, int to) 62 | { 63 | Q_ASSERT_X(from < _data.size(), Q_FUNC_INFO, "from index is not valid"); 64 | Q_ASSERT_X(to < _data.size(), Q_FUNC_INFO, "to index is not valid"); 65 | _dependencies.insert(from, to); 66 | } 67 | 68 | template 69 | void TopSort::addDependency(const T &from, const T &to) 70 | { 71 | addDependency(indexOf(from), indexOf(to)); 72 | } 73 | 74 | // http://www.geeksforgeeks.org/topological-sorting-indegree-based-solution/ 75 | template 76 | QList TopSort::sort() const 77 | { 78 | // Create a vector to store indegrees of all vertices. Initialize all indegrees as 0 79 | QVector inDegree(_data.size(), 0); 80 | 81 | // fill indegrees of vertices -> foreach dep, incease count of target 82 | for(auto it = _dependencies.begin(); it != _dependencies.end(); it++) 83 | inDegree[it.value()]++; 84 | 85 | // Create an queue and enqueue all with indegree 0 86 | QQueue queue; 87 | for(auto i = 0; i < inDegree.size(); i++) 88 | if(inDegree[i] == 0) 89 | queue.enqueue(i); 90 | 91 | QList topOrder; 92 | // One by one dequeue vertices from queue and enqueue if indegree becomes 0 93 | while (!queue.isEmpty()) 94 | { 95 | // dequeue and add it to topological order 96 | auto u = queue.dequeue(); 97 | topOrder.append(u); 98 | 99 | // reduce all deps indegree by 1 and enque ones with 0 100 | for(auto dep : _dependencies.values(u)) { 101 | if (--inDegree[dep] == 0) 102 | queue.enqueue(dep); 103 | } 104 | } 105 | 106 | // Check if there was a cycle 107 | if (topOrder.size() != _data.size()) 108 | return {}; 109 | 110 | // generate result list 111 | QList result; 112 | for(auto i : topOrder) 113 | result.prepend(_data[i]); 114 | return result; 115 | } 116 | 117 | template 118 | int TopSort::indexOf(const T &data) const 119 | { 120 | for(int i = 0, m = _data.size(); i < m; i++) { 121 | if(_comparator(data, _data[i])) 122 | return i; 123 | } 124 | return -1; 125 | } 126 | 127 | #endif // TOPSORT_H 128 | -------------------------------------------------------------------------------- /ci/appveyor_test.bat: -------------------------------------------------------------------------------- 1 | :: build 2 | setlocal 3 | setlocal enabledelayedexpansion 4 | @echo off 5 | 6 | set qtplatform=%PLATFORM% 7 | set "PATH=%CD%\build-%qtplatform%\qpmx\release;%CD%\build-%qtplatform%\lib\release;C:\projects\;C:\projects\Qt\%QT_VER%\%qtplatform%\bin;%PATH%;" 8 | set "QT_PLUGIN_PATH=%CD%\build-%qtplatform%\plugins;%QT_PLUGIN_PATH%" 9 | where qpmx.exe 10 | 11 | if "%qtplatform%" == "msvc2017_64" goto :setup_vc 12 | set PATH=C:\projects\Qt\Tools\mingw530_32\bin;%PATH%; 13 | set MAKEFLAGS=-j%NUMBER_OF_PROCESSORS% 14 | set MAKE=mingw32-make 15 | goto :setup_done 16 | :setup_vc 17 | call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvarsall.bat" amd64 || exit /B 1 18 | set MAKE=nmake 19 | :setup_done 20 | 21 | :: install plugins into qt 22 | :: mkdir C:\projects\Qt\%QT_VER%\%qtplatform%\plugins\qpmx || exit /B 1 23 | :: xcopy /s build-%qtplatform%\plugins\qpmx C:\projects\Qt\%QT_VER%\%qtplatform%\plugins\qpmx || exit /B 1 24 | qpmx list providers || ( 25 | echo Failed to run qpmx with error code %errorlevel% 26 | exit /B %errorlevel% 27 | ) 28 | 29 | :: build tests (bin and src) 30 | :: compile, compile-dev, src-dev, src 31 | set "QMAKE_FLAGS=CONFIG+=debug_and_release %QMAKE_FLAGS%" 32 | for /L %%i IN (0, 1, 3) DO ( 33 | if "%%i" == "1" ( 34 | ren submodules\qpmx-sample-package\qpmx-test\qpmx.json.user.cm qpmx.json.user 35 | ) 36 | if "%%i" == "2" ( 37 | del submodules\qpmx-sample-package\qpmx-test\qpmx.json 38 | ren submodules\qpmx-sample-package\qpmx-test\qpmx.json.src qpmx.json 39 | ) 40 | if "%%i" == "3" ( 41 | del submodules\qpmx-sample-package\qpmx-test\qpmx.json.user 42 | ) 43 | 44 | for /L %%j IN (0, 1, 2) DO ( 45 | echo running test case %%i-%%j 46 | 47 | set "M_FLAGS=%QMAKE_FLAGS%" 48 | if "%%j" == "1" ( 49 | set "M_FLAGS=CONFIG+=test_as_shared %QMAKE_FLAGS%" 50 | ) 51 | if "%%j" == "2" ( 52 | set "M_FLAGS=CONFIG+=test_as_static %QMAKE_FLAGS%" 53 | ) 54 | 55 | mkdir build-%qtplatform%\tests-%%i-%%j 56 | cd build-%qtplatform%\tests-%%i-%%j 57 | 58 | echo running qmake with flags !M_FLAGS! 59 | C:\projects\Qt\%QT_VER%\%qtplatform%\bin\qmake !M_FLAGS! ../../submodules/qpmx-sample-package/qpmx-test/ || ( 60 | for /D %%G in (C:\Users\appveyor\AppData\Local\Temp\1\qpmx*) do ( 61 | echo %%G 62 | type %%G\qmake.stdout.log 63 | type %%G\make.stdout.log 64 | type %%G\install.stdout.log 65 | ) 66 | 67 | type "C:\tmp\.qpmx-dev-cache\build\git\https.3A.2F.2Fgithub.2Ecom.2Fskycoder42.2Fqpmx-sample-package.2Egit\1.0.14\qmake.stdout.log" 68 | type "C:\tmp\.qpmx-dev-cache\build\git\https.3A.2F.2Fgithub.2Ecom.2Fskycoder42.2Fqpmx-sample-package.2Egit\1.0.14\make.stdout.log" 69 | type "C:\tmp\.qpmx-dev-cache\build\git\https.3A.2F.2Fgithub.2Ecom.2Fskycoder42.2Fqpmx-sample-package.2Egit\1.0.14\install.stdout.log" 70 | 71 | exit /B 1 72 | ) 73 | 74 | %MAKE% qmake_all || exit /B 1 75 | :: make all needed because of debug and release... 76 | %MAKE% all || exit /B 1 77 | %MAKE% lrelease || exit /B 1 78 | call :mktemp 79 | %MAKE% INSTALL_ROOT=%TMP_DIR% install || exit /B 1 80 | 81 | if "%%j" == "0" ( 82 | .\release\test.exe || exit /B 1 83 | .\debug\test.exe || exit /B 1 84 | ) 85 | 86 | cd ..\.. 87 | ) 88 | ) 89 | 90 | :: extra tests 91 | :: test install without provider/version 92 | qpmx install -cr --verbose de.skycoder42.qpathedit https://github.com/Skycoder42/qpmx-sample-package.git 93 | 94 | :: create tmp dir 95 | :mktemp 96 | :uniqLoop 97 | set "TMP_DIR=\tmp\install~%RANDOM%" 98 | if exist "C:%TMP_DIR%" goto :uniqLoop 99 | mkdir "C:%TMP_DIR%" 100 | -------------------------------------------------------------------------------- /qpmx/completitions/zsh/_qpmx: -------------------------------------------------------------------------------- 1 | #compdef qpmx 2 | 3 | typeset -A opt_args 4 | setopt extendedglob 5 | 6 | local optargs cmdargs providers 7 | 8 | providers="($(qpmx list providers --short))" 9 | 10 | optargs=( 11 | '(-h --help)'{-h,--help}'[help]' 12 | {-v,--version}'[version]' 13 | '--verbose[show more output]' 14 | {-q,--quiet}'[show less output]' 15 | '--quiet[show less output]' 16 | '--no-color[do not use colors for the output]' 17 | {-d,--dir}'[qpmx file directory]:directory:_path_files -/' 18 | '--dev-cache[the directory to create the dev cache in]:directory:_path_files -/' 19 | ) 20 | 21 | cmdargs=(':first command:(clean-caches compile create dev generate init install list prepare publish qbs search uninstall update)') 22 | 23 | _arguments -C $cmdargs $optargs "*::arg:->args" 24 | 25 | cmdargs=() 26 | case $line[1] in 27 | clean-caches) 28 | optargs=($optargs '--no-src[remove compiled cache only]' {-y,--yes}'[skip confirmation]') 29 | ;; 30 | compile) 31 | optargs=( 32 | $optargs 33 | {-m,--qmake}'[qmake executable]:qmake:_files -g "*qmake*"' 34 | {-g,--global}'[operate in global mode]' 35 | {-r,--recompile}'[recompile already cached depepencies]' 36 | {-e,--stderr}'[forward stderr]' 37 | {-c,--clean}'[enforce clean dev builds]' 38 | ) 39 | ;; 40 | create) 41 | optargs=($optargs {-p,--prepare}"[prepare provider]:provider:$providers") 42 | ;; 43 | dev) 44 | cmdargs=(':subcommands for dev:(add alias commit remove)') 45 | ;; 46 | generate) 47 | optargs=($optargs {-m,--qmake}'[qmake executable]:qmake:_files -g "*qmake*"' {-r,--recreate}'[always create file]') 48 | ;; 49 | init) 50 | optargs=( 51 | $optargs 52 | '-r[pass flag to subcommands]' 53 | {-e,--stderr}'[forward stderr]' 54 | {-c,--clean}'[enforce clean dev builds]' 55 | '--qpmx-prepare[prepare with qpmx deps]:profile:_files -g "*.pro"' 56 | '--ts-prepare[prepare for translation]:profile:_files -g "*.pro"' 57 | ) 58 | ;; 59 | install) 60 | optargs=($optargs {-r,--renew}'[download existing again]' {-c,--cache}'[cache deps only]' '--no-prepare[dont prepare profile]') 61 | ;; 62 | list) 63 | optargs=($optargs '--short[print short version]') 64 | cmdargs=(':subcommands for list:(kits providers)') 65 | ;; 66 | publish) 67 | optargs=($optargs {-p,--provider}"[select provider]:provider:$providers") 68 | ;; 69 | qbs) 70 | optargs=( 71 | $optargs 72 | '--path[qbs binary]:qbs:_files -g "*qbs*"' 73 | {-s,--settings-dir}"[qbs settings dir]:directory:_path_files -/" 74 | ) 75 | cmdargs=(':subcommands for qbs:(init generate load)') 76 | ;; 77 | search) 78 | optargs=($optargs {-p,--provider}"[select provider]:provider:$providers" '--short[print short version]') 79 | ;; 80 | uninstall) 81 | optargs=($optargs {-c,--cache}'[remove from cache]') 82 | ;; 83 | update) 84 | optargs=($optargs {-i,--install}'[install updates]' {-y,--yes}'[skip confirmation]') 85 | ;; 86 | esac 87 | 88 | _arguments -C $optargs $cmdargs "*::arg:->args" 89 | 90 | if [ -n "$cmdargs" ]; then 91 | case $line[2] in 92 | commit) 93 | optargs=($optargs '--no-add[dont add to qpmx.json]' {-p,--provider}"[select provider]:provider:$providers") 94 | ;; 95 | remove) 96 | optargs=($optargs '--alias[remove an alias]') 97 | ;; 98 | init) 99 | optargs=( 100 | $optargs 101 | '-r[pass flag to subcommands]' 102 | {-e,--stderr}'[forward stderr]' 103 | {-c,--clean}'[enforce clean dev builds]' 104 | {-p,--profile}'[qbs profiles]:name:' 105 | '--qbs-version[qbs version]' 106 | ) 107 | ;; 108 | generate) 109 | optargs=( 110 | $optargs 111 | '-r[recreated modules]' 112 | {-p,--profile}'[qbs profiles]:name:' 113 | '--qbs-version[qbs version]' 114 | ) 115 | ;; 116 | esac 117 | 118 | _arguments $optargs $cmdargs 119 | fi 120 | 121 | -------------------------------------------------------------------------------- /qpmx/searchcommand.cpp: -------------------------------------------------------------------------------- 1 | #include "searchcommand.h" 2 | #include 3 | #include 4 | using namespace qpmx; 5 | 6 | #define print(x) std::cout << QString(x).toStdString() << std::endl 7 | 8 | SearchCommand::SearchCommand(QObject *parent) : 9 | Command{parent} 10 | {} 11 | 12 | QString SearchCommand::commandName() const 13 | { 14 | return QStringLiteral("search"); 15 | } 16 | 17 | QString SearchCommand::commandDescription() const 18 | { 19 | return tr("Search for a package by it's name."); 20 | } 21 | 22 | QSharedPointer SearchCommand::createCliNode() const 23 | { 24 | auto searchNode = QSharedPointer::create(); 25 | searchNode->addOption({ 26 | {QStringLiteral("p"), QStringLiteral("provider")}, 27 | tr("Specify the to search for this package. Can be specified " 28 | "multiple times to search multiple providers. If not specified, all " 29 | "providers that support searching are searched."), 30 | tr("provider") 31 | }); 32 | searchNode->addOption({ 33 | QStringLiteral("short"), 34 | QStringLiteral("Only list package names (with provider) as space seperated list, no category based listing.") 35 | }); 36 | searchNode->addPositionalArgument(QStringLiteral("query"), 37 | tr("The query to search by. Typically, a \"contains\" search is " 38 | "performed, but some providers may support wildcard or regex expressions. " 39 | "If you can't find a package, try a different search pattern first.")); 40 | return searchNode; 41 | } 42 | 43 | void SearchCommand::initialize(QCliParser &parser) 44 | { 45 | try { 46 | _short = parser.isSet(QStringLiteral("short")); 47 | 48 | if(parser.positionalArguments().isEmpty()) 49 | throw tr("You must specify a search query to perform a search"); 50 | auto query = parser.positionalArguments().join(QLatin1Char(' ')); 51 | 52 | auto providers = parser.values(QStringLiteral("provider")); 53 | if(!providers.isEmpty()) { 54 | for(const auto &provider : providers) { 55 | if(!registry()->sourcePlugin(provider)->canSearch(provider)) 56 | throw tr("Provider %{bld}%1%{end} does not support searching").arg(provider); 57 | } 58 | } else { 59 | for(const auto &provider : registry()->providerNames()) { 60 | if(registry()->sourcePlugin(provider)->canSearch(provider)) 61 | providers.append(provider); 62 | } 63 | xDebug() << tr("Searching providers: %1").arg(providers.join(tr(", "))); 64 | } 65 | 66 | performSearch(query, providers); 67 | } catch(QString &s) { 68 | xCritical() << s; 69 | } 70 | } 71 | 72 | void SearchCommand::performSearch(const QString &query, const QStringList &providers) 73 | { 74 | QtCoroutine::awaitEach(providers, [this, query](const QString &provider){ 75 | try { 76 | auto plg = registry()->sourcePlugin(provider); 77 | auto packageNames = plg->searchPackage(provider, query); 78 | xDebug() << tr("Found %n result(s) for provider %{bld}%1%{end}", "", packageNames.size()).arg(provider); 79 | if(!packageNames.isEmpty()) 80 | _searchResults.append({provider, packageNames}); 81 | } catch(qpmx::SourcePluginException &e) { 82 | throw tr("Failed to search provider %{bld}%1%{end} with error: %2") 83 | .arg(provider, e.qWhat()); 84 | } 85 | }); 86 | 87 | printResult(); 88 | } 89 | 90 | void SearchCommand::printResult() 91 | { 92 | if(_short) { 93 | QStringList resList; 94 | for(const auto &res : qAsConst(_searchResults)) { 95 | for(const auto &pkg : res.second) 96 | resList.append(PackageInfo(res.first, pkg).toString(false)); 97 | } 98 | print(resList.join(QLatin1Char(' '))); 99 | } else { 100 | for(const auto &res : qAsConst(_searchResults)) { 101 | print(tr("--- %1").arg(res.first + QLatin1Char(' '), -76, QLatin1Char('-'))); 102 | for(const auto &pkg : res.second) 103 | print(tr(" %1").arg(pkg)); 104 | } 105 | } 106 | 107 | qApp->quit(); 108 | } 109 | -------------------------------------------------------------------------------- /qpmx/updatecommand.cpp: -------------------------------------------------------------------------------- 1 | #include "updatecommand.h" 2 | #include 3 | 4 | UpdateCommand::UpdateCommand(QObject *parent) : 5 | Command{parent} 6 | {} 7 | 8 | QString UpdateCommand::commandName() const 9 | { 10 | return QStringLiteral("update"); 11 | } 12 | 13 | QString UpdateCommand::commandDescription() const 14 | { 15 | return tr("Update all dependencies of a qpmx.json file to the newest available version."); 16 | } 17 | 18 | QSharedPointer UpdateCommand::createCliNode() const 19 | { 20 | auto updateNode = QSharedPointer::create(); 21 | updateNode->addOption({ 22 | {QStringLiteral("i"), QStringLiteral("install")}, 23 | tr("Don't just update the dependencies, also download the newer sources. " 24 | "(This is done automatically on the next qmake, or by running qpmx install)"), 25 | }); 26 | updateNode->addOption({ 27 | {QStringLiteral("y"), QStringLiteral("yes")}, 28 | tr("Skip the prompt before updating all dependencies."), 29 | }); 30 | return updateNode; 31 | } 32 | 33 | void UpdateCommand::initialize(QCliParser &parser) 34 | { 35 | try { 36 | _install = parser.isSet(QStringLiteral("install")); 37 | _skipYes = parser.isSet(QStringLiteral("yes")); 38 | 39 | auto format = QpmxFormat::readDefault(true); //no user format, as only real deps are updated 40 | _pkgList = format.dependencies; 41 | if(_pkgList.isEmpty()) { 42 | xWarning() << tr("No dependencies found in qpmx.json. Nothing will be done"); 43 | qApp->quit(); 44 | return; 45 | } 46 | 47 | xDebug() << tr("Searching for updates for %n package(s) from qpmx.json file", "", _pkgList.size()); 48 | checkPackages(); 49 | } catch(QString &s) { 50 | xCritical() << s; 51 | } 52 | } 53 | 54 | void UpdateCommand::checkPackages() 55 | { 56 | quint32 currentLoad = 0; 57 | QQueue checkQueue; 58 | QtCoroutine::awaitEach(_pkgList, [this, &checkQueue, ¤tLoad](const QpmxDependency &nextDep){ 59 | if(currentLoad >= LoadLimit) { 60 | checkQueue.enqueue(QtCoroutine::current()); 61 | QtCoroutine::yield(); 62 | } 63 | 64 | auto plugin = registry()->sourcePlugin(nextDep.provider); 65 | if(!plugin->packageValid(nextDep.pkg())) { 66 | throw tr("The package name %1 is not valid for provider %{bld}%2%{end}") 67 | .arg(nextDep.package, nextDep.provider); 68 | } 69 | 70 | xDebug() << tr("Searching for latest version of %1").arg(nextDep.toString()); 71 | //use the latest version -> query for it 72 | ++currentLoad; 73 | try { 74 | auto version = plugin->findPackageVersion(nextDep.pkg()); 75 | if(version > nextDep.version) 76 | _updateList.append({nextDep, version}); 77 | } catch (qpmx::SourcePluginException &e) { 78 | xWarning() << tr("Failed to fetch latest version of %1 with error: %2").arg(nextDep.toString()) 79 | << e.what(); 80 | } 81 | --currentLoad; 82 | 83 | if(!checkQueue.isEmpty()) 84 | QtCoroutine::resume(checkQueue.dequeue()); 85 | }); 86 | 87 | if(!_updateList.isEmpty()) 88 | completeUpdate(); 89 | else 90 | xInfo() << tr("All packages are up to date"); 91 | qApp->quit(); 92 | } 93 | 94 | void UpdateCommand::completeUpdate() 95 | { 96 | QString pkgStr; 97 | for(const auto &p : qAsConst(_updateList)) { 98 | pkgStr.append(tr("\n * %1 -> %{bld}%2%{end}") 99 | .arg(p.first.toString(), p.second.toString())); 100 | } 101 | 102 | if(!_skipYes) { 103 | QFile console; 104 | if(!console.open(stdin, QIODevice::ReadOnly | QIODevice::Text)) 105 | throw tr("Failed to access console with error: %1").arg(console.errorString()); 106 | QTextStream stream(&console); 107 | 108 | xInfo() << tr("Update are available for the packages:") << pkgStr; 109 | if(!readBool(tr("Do you want to update all packages to the newer versions? (%1) "), stream, true)) { 110 | xDebug() << tr("Update of qpmx.json was canceled"); 111 | return; 112 | } 113 | } else 114 | xInfo() << tr("Updating the following packages:") << pkgStr; 115 | 116 | auto format = QpmxFormat::readDefault(true); 117 | for(const auto &p : qAsConst(_updateList)) { 118 | auto dIndex = format.dependencies.indexOf(p.first); 119 | if(dIndex != -1) 120 | format.dependencies[dIndex].version = p.second; 121 | else 122 | xWarning() << tr("Updated dependency %1 cannot be found in qpmx.json. Skipping update"); 123 | } 124 | 125 | QpmxFormat::writeDefault(format); 126 | xDebug() << tr("Update all dependencies to their newest version"); 127 | 128 | if(_install) 129 | subCall({QStringLiteral("install")}); 130 | } 131 | -------------------------------------------------------------------------------- /qpmx/command.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMAND_H 2 | #define COMMAND_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "packageinfo.h" 13 | #include "pluginregistry.h" 14 | #include "qpmxformat.h" 15 | 16 | namespace qpmx { 17 | namespace priv { 18 | class Bridge; 19 | } 20 | } 21 | 22 | class Command : public QObject 23 | { 24 | Q_OBJECT 25 | 26 | Q_PROPERTY(bool devMode READ devMode WRITE setDevMode) 27 | 28 | public: 29 | explicit Command(QObject *parent = nullptr); 30 | 31 | virtual QString commandName() const = 0; 32 | virtual QString commandDescription() const = 0; 33 | virtual QSharedPointer createCliNode() const = 0; 34 | 35 | static void setupParser(QCliParser &parser, const QHash &commands); 36 | 37 | void init(QCliParser &parser); 38 | void fin(); 39 | 40 | static int exitCode(); 41 | static QDir subDir(QDir dir, const QString &provider, const QString &package, const QVersionNumber &version, bool mkDir); 42 | 43 | protected slots: 44 | virtual void initialize(QCliParser &parser) = 0; 45 | virtual void finalize(); 46 | 47 | protected: 48 | static int _ExitCode; 49 | 50 | struct BuildId : public QString 51 | { 52 | inline BuildId() = default; 53 | inline BuildId(const QString &other) : 54 | QString{other} 55 | {} 56 | inline BuildId(QUuid other) : 57 | #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) 58 | QString{other.toString(QUuid::WithoutBraces)} 59 | {} 60 | #else 61 | QString(other.toString()) 62 | { 63 | *this = mid(1, size() - 2); 64 | } 65 | #endif 66 | }; 67 | 68 | class CacheLock 69 | { 70 | friend class Command; 71 | friend class SharedCacheLock; 72 | Q_DISABLE_COPY(CacheLock) 73 | 74 | public: 75 | CacheLock(); 76 | CacheLock(CacheLock &&mv) noexcept; 77 | CacheLock &operator=(CacheLock &&mv) noexcept; 78 | ~CacheLock(); 79 | 80 | bool isLocked() const; 81 | void free(); 82 | void relock(); 83 | 84 | private: 85 | CacheLock(const QString &path, int timeout); 86 | QString _path; 87 | QScopedPointer _lock; 88 | 89 | void doLock(); 90 | }; 91 | 92 | PluginRegistry *registry() const; 93 | QSettings *settings() const; 94 | 95 | void setDevMode(bool devModeActive); 96 | bool devMode() const; 97 | 98 | Q_REQUIRED_RESULT CacheLock pkgLock(const qpmx::PackageInfo &package) const; 99 | Q_REQUIRED_RESULT CacheLock pkgLock(const QpmxDependency &dep) const; 100 | Q_REQUIRED_RESULT CacheLock pkgLock(const QpmxDevDependency &dep) const; 101 | Q_REQUIRED_RESULT CacheLock kitLock() const; 102 | 103 | QList readCliPackages(const QStringList &arguments, bool fullPkgOnly = false) const; 104 | static QList depList(const QList &pkgList); 105 | static QList devDepList(const QList &pkgList); 106 | static void replaceAlias(QpmxDependency &original, const QList &aliases); 107 | 108 | void cleanCaches(const qpmx::PackageInfo &package, const CacheLock &srcLockRef) const; 109 | 110 | bool readBool(const QString &message, QTextStream &stream, bool defaultValue) const; 111 | void printTable(const QStringList &headers, const QList &minimals, const QList &rows) const; 112 | void subCall(QStringList arguments, const QString &workingDir = {}) const; 113 | 114 | QDir srcDir() const; 115 | QDir srcDir(const qpmx::PackageInfo &package, bool mkDir = false) const; 116 | QDir srcDir(const QpmxDependency &dep, bool mkDir = false) const; 117 | QDir srcDir(const QpmxDevDependency &dep, bool mkDir = false) const; 118 | QDir srcDir(const QString &provider, const QString &package, const QVersionNumber &version, bool mkDir = false) const; 119 | 120 | QDir buildDir() const; 121 | QDir buildDir(const BuildId &kitId) const; 122 | QDir buildDir(const BuildId &kitId, const qpmx::PackageInfo &package, bool mkDir = false) const; 123 | QDir buildDir(const BuildId &kitId, const QpmxDependency &dep, bool mkDir = false) const; 124 | QDir buildDir(const BuildId &kitId, const QString &provider, const QString &package, const QVersionNumber &version, bool mkDir = false) const; 125 | 126 | QDir tmpDir() const; 127 | QDir lockDir(bool asDev) const; 128 | 129 | static QString pkgEncode(const QString &name); 130 | static QString pkgDecode(QString name); 131 | static QString dashed(const QString &option); 132 | 133 | private: 134 | friend class qpmx::priv::Bridge; 135 | 136 | PluginRegistry *_registry; 137 | QSettings *_settings; 138 | bool _devMode = false; 139 | 140 | bool _verbose = false; 141 | bool _quiet = false; 142 | #ifndef Q_OS_WIN 143 | bool _noColor = false; 144 | #endif 145 | bool _qmakeRun = false; 146 | QString _cacheDir; 147 | 148 | QDir cacheDir() const; 149 | Q_REQUIRED_RESULT CacheLock lock(const QString &name, bool asDev = false) const; 150 | }; 151 | 152 | #define xDebug(...) qDebug(__VA_ARGS__).noquote() 153 | #define xInfo(...) qInfo(__VA_ARGS__).noquote() 154 | #define xWarning(...) qWarning(__VA_ARGS__).noquote() 155 | #define xCritical(...) qCritical(__VA_ARGS__).noquote() 156 | 157 | #endif // COMMAND_H 158 | -------------------------------------------------------------------------------- /qpmx/hookcommand.cpp: -------------------------------------------------------------------------------- 1 | #include "hookcommand.h" 2 | 3 | #include 4 | using namespace qpmx; 5 | 6 | HookCommand::HookCommand(QObject *parent) : 7 | Command(parent) 8 | {} 9 | 10 | QString HookCommand::commandName() const 11 | { 12 | return QStringLiteral("hook"); 13 | } 14 | 15 | QString HookCommand::commandDescription() const 16 | { 17 | return tr("Creates a source file for the given hook id"); 18 | } 19 | 20 | QSharedPointer HookCommand::createCliNode() const 21 | { 22 | auto hookNode = QSharedPointer::create(); 23 | hookNode->setHidden(true); 24 | hookNode->addOption({ 25 | QStringLiteral("prepare"), 26 | tr("Generate the special sources for given as outfile."), 27 | tr("source") 28 | }); 29 | hookNode->addOption({ 30 | {QStringLiteral("o"), QStringLiteral("out")}, 31 | tr("The of the file to be generated (required!)."), 32 | tr("path") 33 | }); 34 | hookNode->addPositionalArgument(QStringLiteral("hook_ids"), 35 | tr("The ids of the hooks to be added to the hookup. Typically defined by the " 36 | "QPMX_STARTUP_HASHES qmake variable."), 37 | QStringLiteral("[ ...]")); 38 | return hookNode; 39 | } 40 | 41 | void HookCommand::initialize(QCliParser &parser) 42 | { 43 | try { 44 | auto outFile = parser.value(QStringLiteral("out")); 45 | if(outFile.isEmpty()) 46 | throw tr("You must specify the name of the file to generate as --out option"); 47 | 48 | QFile out(outFile); 49 | if(!out.open(QIODevice::WriteOnly | QIODevice::Text)) { 50 | throw tr("Failed to create %1 file with error: %2") 51 | .arg(out.fileName(), out.errorString()); 52 | } 53 | 54 | if(parser.isSet(QStringLiteral("prepare"))) 55 | createHookCompile(parser.value(QStringLiteral("prepare")), &out); 56 | else 57 | createHookSrc(parser.positionalArguments(), &out); 58 | 59 | out.close(); 60 | qApp->quit(); 61 | } catch (QString &s) { 62 | xCritical() << s; 63 | } 64 | } 65 | 66 | void HookCommand::createHookSrc(const QStringList &args, QIODevice *out) 67 | { 68 | bool sep = false; 69 | QRegularExpression replaceRegex(QStringLiteral(R"__([\.-])__")); 70 | QStringList hooks; 71 | QStringList resources; 72 | for(auto arg : args) { 73 | if(sep) 74 | resources.append(arg.replace(replaceRegex, QStringLiteral("_"))); 75 | else if(arg == QStringLiteral("%%")) 76 | sep = true; 77 | else 78 | hooks.append(arg); 79 | } 80 | 81 | xDebug() << tr("Creating hook file"); 82 | QTextStream stream(out); 83 | stream << "#include \n\n" 84 | << "namespace __qpmx_startup_hooks {\n"; 85 | for(const auto &hook : hooks) 86 | stream << "\tvoid hook_" << hook << "();\n"; 87 | stream << "}\n\n"; 88 | stream << "using namespace __qpmx_startup_hooks;\n" 89 | << "static void __qpmx_root_hook() {\n"; 90 | for(const auto &resource : resources) 91 | stream << "\tQ_INIT_RESOURCE(" << resource << ");\n"; 92 | for(const auto &hook : hooks) 93 | stream << "\thook_" << hook << "();\n"; 94 | stream << "}\n" 95 | << "Q_CONSTRUCTOR_FUNCTION(__qpmx_root_hook)\n"; 96 | stream.flush(); 97 | } 98 | 99 | void HookCommand::createHookCompile(const QString &inFile, QIODevice *out) 100 | { 101 | xDebug() << tr("Scanning %1 for startup hooks").arg(inFile); 102 | 103 | QStringList functions; 104 | QFile file(inFile); 105 | if(!file.open(QIODevice::ReadOnly | QIODevice::Text)) { 106 | throw tr("Failed to read source file %1 with error: %2") 107 | .arg(file.fileName(), file.errorString()); 108 | } 109 | auto inData = QTextStream(&file).readAll(); 110 | QRegularExpression fileRegex(QStringLiteral(R"__(^Q_COREAPP_STARTUP_FUNCTION\(([^\)]+)\)$)__"), 111 | QRegularExpression::MultilineOption); 112 | auto iter = fileRegex.globalMatch(inData); 113 | while(iter.hasNext()) { 114 | auto match = iter.next(); 115 | functions.append(match.captured(1)); 116 | } 117 | xDebug() << tr("found startup hooks: %1").arg(functions.join(QStringLiteral(", "))); 118 | 119 | xDebug() << tr("Creating hook include"); 120 | QTextStream stream(out); 121 | stream << "#define Q_CONSTRUCTOR_FUNCTION(x)\n" //define to nothing to prevent code generation 122 | << "#include \"" << inFile << "\"\n"; 123 | 124 | QDir hookDir(QStringLiteral(".qpmx_startup_hooks")); 125 | if(!hookDir.mkpath(QStringLiteral("."))) 126 | throw tr("Failed to create hook directory"); 127 | QFile hookFile(hookDir.absoluteFilePath(QFileInfo(inFile).fileName())); 128 | if(!functions.isEmpty()) { 129 | if(!hookFile.open(QIODevice::WriteOnly | QIODevice::Text)) 130 | throw tr("Failed to create qpmx hook cache with error: %1").arg(hookFile.errorString()); 131 | QTextStream hookStream(&hookFile); 132 | 133 | stream << "\nnamespace __qpmx_startup_hooks {"; 134 | for(const auto &fn : functions) { 135 | auto fnId = QCryptographicHash::hash(inFile.toUtf8() + fn.toUtf8(), QCryptographicHash::Sha3_256) 136 | .toHex(); 137 | stream << "\n\tvoid hook_" << fnId << "() {\n" 138 | << "\t\t" << fn << "_ctor_function();\n" 139 | << "\t}\n"; 140 | hookStream << fnId << "\n"; 141 | } 142 | stream << "}\n"; 143 | 144 | hookStream.flush(); 145 | hookFile.close(); 146 | } else 147 | hookFile.remove(); 148 | 149 | stream.flush(); 150 | } 151 | -------------------------------------------------------------------------------- /qpmx/qpmxformat.h: -------------------------------------------------------------------------------- 1 | #ifndef QPMXFORMAT_H 2 | #define QPMXFORMAT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "packageinfo.h" 14 | 15 | class QpmxDependency 16 | { 17 | Q_GADGET 18 | 19 | Q_PROPERTY(QString provider MEMBER provider) 20 | Q_PROPERTY(QString package MEMBER package) 21 | Q_PROPERTY(QVersionNumber version MEMBER version) 22 | 23 | public: 24 | QpmxDependency(); 25 | QpmxDependency(const qpmx::PackageInfo &package); 26 | virtual ~QpmxDependency(); 27 | 28 | bool operator==(const QpmxDependency &other) const; 29 | bool operator!=(const QpmxDependency &other) const; 30 | 31 | QString toString(bool scoped = true) const; 32 | bool isComplete() const; 33 | qpmx::PackageInfo pkg(const QString &provider = {}) const; 34 | 35 | QString provider; 36 | QString package; 37 | QVersionNumber version; 38 | }; 39 | 40 | class QpmxFormatLicense 41 | { 42 | Q_GADGET 43 | 44 | Q_PROPERTY(QString name MEMBER name) 45 | Q_PROPERTY(QString file MEMBER file) 46 | 47 | public: 48 | QString name; 49 | QString file; 50 | 51 | bool operator!=(const QpmxFormatLicense &other) const; 52 | 53 | }; 54 | 55 | class QpmxFormat 56 | { 57 | Q_GADGET 58 | Q_DECLARE_TR_FUNCTIONS(QpmxFormat) 59 | 60 | Q_PROPERTY(QString priFile MEMBER priFile) 61 | Q_PROPERTY(QString prcFile MEMBER prcFile) 62 | Q_PROPERTY(QString qbsFile MEMBER qbsFile) 63 | Q_PROPERTY(bool source MEMBER source) 64 | 65 | Q_PROPERTY(QList dependencies MEMBER dependencies) 66 | Q_PROPERTY(QStringList priIncludes MEMBER priIncludes) 67 | 68 | Q_PROPERTY(QpmxFormatLicense license MEMBER license) 69 | #ifdef Q_MOC_RUN //workaround for clang code model 70 | Q_PROPERTY(QMap publishers MEMBER publishers); 71 | #endif 72 | 73 | public: 74 | virtual ~QpmxFormat(); 75 | 76 | static QpmxFormat readFile(const QDir &dir, bool mustExist = false); 77 | static QpmxFormat readFile(const QDir &dir, const QString &fileName, bool mustExist = false); 78 | static QpmxFormat readDefault(bool mustExist = false); 79 | static void writeDefault(const QpmxFormat &data); 80 | 81 | QString priFile; 82 | QString prcFile; 83 | QString qbsFile; 84 | bool source = false; 85 | QList dependencies; 86 | QStringList priIncludes; 87 | QpmxFormatLicense license; 88 | QMap publishers; 89 | 90 | void putDependency(const QpmxDependency &dep); 91 | 92 | protected: 93 | virtual void checkDuplicates(); 94 | template 95 | static void checkDuplicatesImpl(const QList &data); 96 | }; 97 | 98 | class QpmxDevDependency : public QpmxDependency 99 | { 100 | Q_GADGET 101 | 102 | Q_PROPERTY(QString path MEMBER path) 103 | 104 | public: 105 | QpmxDevDependency(); 106 | QpmxDevDependency(const QpmxDependency &dep, QString localPath = {}); 107 | 108 | bool isDev() const; 109 | 110 | bool operator==(const QpmxDependency &other) const; 111 | 112 | QString path; 113 | }; 114 | 115 | class QpmxDevAlias 116 | { 117 | Q_GADGET 118 | 119 | Q_PROPERTY(QpmxDependency original MEMBER original) 120 | Q_PROPERTY(QpmxDependency alias MEMBER alias) 121 | 122 | public: 123 | QpmxDevAlias(); 124 | QpmxDevAlias(const QpmxDependency &original, const QpmxDependency &alias = {}); 125 | 126 | bool operator==(const QpmxDependency &original) const; 127 | bool operator==(const QpmxDevAlias &other) const; 128 | 129 | QpmxDependency original; 130 | QpmxDependency alias; 131 | }; 132 | 133 | class QpmxUserFormat : public QpmxFormat 134 | { 135 | Q_GADGET 136 | Q_DECLARE_TR_FUNCTIONS(QpmxUserFormat) 137 | 138 | Q_PROPERTY(QList devDependencies MEMBER devDependencies) 139 | Q_PROPERTY(QList devAliases MEMBER devAliases) 140 | 141 | //MAJOR keep for compability. Will not be written, but can be read 142 | Q_PROPERTY(QList devmode WRITE setDevmodeSafe READ readDummy) 143 | 144 | public: 145 | QpmxUserFormat(); 146 | QpmxUserFormat(const QpmxUserFormat &userFormat, const QpmxFormat &format); 147 | 148 | QList allDeps() const; 149 | bool hasDevOptions() const; 150 | 151 | static QpmxUserFormat readDefault(bool mustExist = false); 152 | static QpmxUserFormat readFile(const QDir &dir, const QString &fileName, bool mustExist = false); 153 | 154 | static void writeUser(const QpmxUserFormat &data); 155 | 156 | QList devDependencies; 157 | QList devAliases; 158 | 159 | protected: 160 | void checkDuplicates() override; 161 | 162 | private: 163 | void setDevmodeSafe(const QList &data); 164 | QList readDummy() const; 165 | }; 166 | 167 | class QpmxCacheFormat : public QpmxUserFormat 168 | { 169 | Q_GADGET 170 | Q_DECLARE_TR_FUNCTIONS(QpmxCacheFormat) 171 | 172 | Q_PROPERTY(QString buildKit MEMBER buildKit) 173 | 174 | public: 175 | QpmxCacheFormat(); 176 | QpmxCacheFormat(const QpmxUserFormat &userFormat, QString kitId); 177 | 178 | static QpmxCacheFormat readCached(const QDir &dir); 179 | static bool writeCached(const QDir &dir, const QpmxCacheFormat &data); 180 | 181 | QString buildKit; 182 | }; 183 | 184 | Q_DECLARE_METATYPE(QpmxDependency) 185 | Q_DECLARE_METATYPE(QpmxFormatLicense) 186 | Q_DECLARE_METATYPE(QpmxFormat) 187 | Q_DECLARE_METATYPE(QpmxDevDependency) 188 | Q_DECLARE_METATYPE(QpmxDevAlias) 189 | Q_DECLARE_METATYPE(QpmxUserFormat) 190 | Q_DECLARE_METATYPE(QpmxCacheFormat) 191 | 192 | #endif // QPMXFORMAT_H 193 | -------------------------------------------------------------------------------- /qpmx/createcommand.cpp: -------------------------------------------------------------------------------- 1 | #include "createcommand.h" 2 | 3 | #include 4 | using namespace qpmx; 5 | 6 | #define print(x) do { \ 7 | std::cout << QString(x).toStdString(); \ 8 | std::cout.flush(); \ 9 | } while(false) 10 | 11 | CreateCommand::CreateCommand(QObject *parent) : 12 | Command{parent} 13 | {} 14 | 15 | QString CreateCommand::commandName() const 16 | { 17 | return QStringLiteral("create"); 18 | } 19 | 20 | QString CreateCommand::commandDescription() const 21 | { 22 | return tr("Create a new qpmx package by creating the initial qpmx.json " 23 | "(or setting up an exiting one)."); 24 | } 25 | 26 | QSharedPointer CreateCommand::createCliNode() const 27 | { 28 | auto createNode = QSharedPointer::create(); 29 | createNode->addOption({ 30 | {QStringLiteral("p"), QStringLiteral("prepare")}, 31 | tr("After creating the qpmx.json, prepare it for the given . " 32 | "Can be specified multiple times. Preparing is needed for later publishing via qpmx."), 33 | tr("provider") 34 | }); 35 | return createNode; 36 | } 37 | 38 | void CreateCommand::initialize(QCliParser &parser) 39 | { 40 | try { 41 | if(!parser.positionalArguments().isEmpty()) 42 | throw tr("No additional arguments allowed"); 43 | 44 | runBaseInit(); 45 | 46 | auto providers = parser.values(QStringLiteral("prepare")); 47 | if(!providers.isEmpty()) { 48 | for(const auto &provider : providers) 49 | runPrepare(provider); 50 | } 51 | 52 | xDebug() << tr("Finish qpmx.json creation"); 53 | qApp->quit(); 54 | } catch (QString &s) { 55 | xCritical() << s; 56 | } 57 | } 58 | 59 | void CreateCommand::runBaseInit() 60 | { 61 | QFile console; 62 | if(!console.open(stdin, QIODevice::ReadOnly | QIODevice::Text)) 63 | throw tr("Failed to access console with error: %1").arg(console.errorString()); 64 | QTextStream stream(&console); 65 | 66 | auto format = QpmxFormat::readDefault(); 67 | 68 | if(format.priFile.isEmpty()) 69 | format.priFile = QStringLiteral("qpmx.pri"); 70 | print(tr("Enter the name of the build pri-file [%1]: ").arg(format.priFile)); 71 | auto read = stream.readLine().trimmed(); 72 | if(!read.isEmpty()) 73 | format.priFile = read; 74 | 75 | format.source = readBool(tr("Source builds only? (%1) "), stream, format.source); 76 | 77 | if(!format.source) { 78 | if(format.prcFile.isEmpty()) 79 | print(tr("Enter the name of the compiled include pri-file (optional): ")); 80 | else 81 | print(tr("Enter the name of the compiled include pri-file [%1]: ").arg(format.prcFile)); 82 | read = stream.readLine().trimmed(); 83 | if(!read.isEmpty()) 84 | format.prcFile = read; 85 | } 86 | 87 | if(format.license.name.isEmpty()) 88 | print(tr("Enter the type of license your are using (optional, e.g. \"MIT\"): ")); 89 | else 90 | print(tr("Enter the type of license your are using [%1]: ").arg(format.license.name)); 91 | read = stream.readLine().trimmed(); 92 | if(!read.isEmpty()) 93 | format.license.name = read; 94 | 95 | if(format.license.file.isEmpty()) { 96 | //try to find the default license file 97 | auto dir = QDir::current(); 98 | dir.setFilter(QDir::Files | QDir::Readable); 99 | dir.setSorting(QDir::Name); 100 | dir.setNameFilters({QStringLiteral("*LICENSE*")}); 101 | auto list = dir.entryList(); 102 | if(!list.isEmpty()) 103 | format.license.file = list.first(); 104 | else 105 | format.license.file = QStringLiteral("LICENSE"); 106 | } 107 | print(tr("Enter the file name of the license your are using [%1]: ").arg(format.license.file)); 108 | read = stream.readLine().trimmed(); 109 | if(!read.isEmpty()) 110 | format.license.file = read; 111 | 112 | auto generate = readBool(tr("Generate templates for non existant files? (%1) "), stream, true); 113 | 114 | QpmxFormat::writeDefault(format); 115 | xDebug() << tr("Created qpmx.json file"); 116 | 117 | if(generate) { 118 | if(!QFile::exists(format.priFile)) { 119 | if(!QFile::copy(QStringLiteral(":/build/default.pri"), format.priFile)) 120 | throw tr("Failed to create %1 file").arg(format.priFile); 121 | 122 | QFile priFile(format.priFile); 123 | priFile.setPermissions(priFile.permissions() | QFile::WriteUser | QFile::WriteOwner); 124 | if(!format.prcFile.isEmpty()) { 125 | if(!priFile.open(QIODevice::Append | QIODevice::Text)) { 126 | throw tr("Failed to open %1 file with error: %2") 127 | .arg(priFile.fileName(), priFile.errorString()); 128 | } 129 | 130 | QTextStream priStream(&priFile); 131 | priStream << "\ninclude($$PWD/" << format.prcFile << ")\n"; 132 | priStream.flush(); 133 | priFile.close(); 134 | xDebug() << tr("Created default pri-file with prc include"); 135 | } else 136 | xDebug() << tr("Created default pri-file"); 137 | } 138 | 139 | if(!format.prcFile.isEmpty() && !QFile::exists(format.prcFile)) { 140 | QFile prcFile(format.prcFile); 141 | if(!prcFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 142 | throw tr("Failed to create %1 file with error: %2") 143 | .arg(prcFile.fileName(), prcFile.errorString()); 144 | } 145 | prcFile.write("\n"); 146 | prcFile.close(); 147 | xDebug() << tr("Created empty prc-file"); 148 | } 149 | } 150 | } 151 | 152 | void CreateCommand::runPrepare(const QString &provider) 153 | { 154 | QStringList args { 155 | QStringLiteral("prepare"), 156 | provider 157 | }; 158 | 159 | xInfo() << tr("\nPreparing qpmx.json for provider %{bld}%1%{end}").arg(provider); 160 | subCall(args); 161 | xDebug() << tr("Successfully prepare for provider %{bld}%1%{end}").arg(provider); 162 | } 163 | -------------------------------------------------------------------------------- /qpmx/initcommand.cpp: -------------------------------------------------------------------------------- 1 | #include "initcommand.h" 2 | using namespace qpmx; 3 | 4 | InitCommand::InitCommand(QObject *parent) : 5 | Command(parent) 6 | {} 7 | 8 | QString InitCommand::commandName() const 9 | { 10 | return QStringLiteral("init"); 11 | } 12 | 13 | QString InitCommand::commandDescription() const 14 | { 15 | return tr("Initialize a qpmx based project by downloading and compiling sources, " 16 | "as well as generation the required includes. Call\n" 17 | "qpmx init --prepare \n" 18 | "to prepare a pro file to automatically initialize with qmake."); 19 | } 20 | 21 | QSharedPointer InitCommand::createCliNode() const 22 | { 23 | auto initNode = QSharedPointer::create(); 24 | initNode->addOption({ 25 | QStringLiteral("r"), 26 | tr("Pass the -r option to the install, compile and generate commands."), 27 | }); 28 | initNode->addOption({ 29 | {QStringLiteral("e"), QStringLiteral("stderr")}, 30 | tr("Pass the --stderr cli flag to to compile step. This will forward stderr of " 31 | "subprocesses instead of logging to a file."), 32 | }); 33 | initNode->addOption({ 34 | {QStringLiteral("c"), QStringLiteral("clean")}, 35 | tr("Pass the --clean cli flag to the compile step. This will generate clean dev " 36 | "builds instead of caching them for speeding builds up."), 37 | }); 38 | initNode->addOption({ 39 | QStringLiteral("qpmx-prepare"), 40 | tr("Prepare the given by adding the qpmx initializations lines. By using this " 41 | "option, no initialization is performed."), 42 | tr("pro-file") 43 | }); 44 | initNode->addOption({ 45 | QStringLiteral("ts-prepare"), 46 | tr("Prepare the given subdirs by adding the lrelease target. This is useful for parent " 47 | "pro-files of qpmx pro-files, as it allows you to call \"make lrelease\" on the subdirs project. " 48 | "By using this option, no initialization is performed."), 49 | tr("pro-file") 50 | }); 51 | initNode->addPositionalArgument(QStringLiteral("qmake-path"), 52 | tr("The path to the qmake to use for compilation of the qpmx dependencies.")); 53 | initNode->addPositionalArgument(QStringLiteral("outdir"), 54 | tr("The directory to generate the files in. Passed to the generate step.")); 55 | return initNode; 56 | 57 | } 58 | 59 | void InitCommand::prepare(const QString &proFile, bool info) 60 | { 61 | QFile file(proFile); 62 | if(!file.exists()) 63 | throw tr("Target file \"%1\" does not exist").arg(proFile); 64 | if(!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) 65 | throw tr("Failed to open pro file \"%1\" with error: %2").arg(proFile, file.errorString()); 66 | 67 | QTextStream stream(&file); 68 | stream << "\n!ReleaseBuild:!DebugBuild:!system(qpmx -d $$shell_quote($$_PRO_FILE_PWD_) --qmake-run init $$QPMX_EXTRA_OPTIONS $$shell_quote($$QMAKE_QMAKE) $$shell_quote($$OUT_PWD)): " 69 | << "error(" << tr("qpmx initialization failed. Check the compilation log for details.") << ")\n" 70 | << "else: include($$OUT_PWD/qpmx_generated.pri)\n"; 71 | stream.flush(); 72 | file.close(); 73 | 74 | auto str = tr("Successfully added qpmx init code to pro file \"%1\"").arg(proFile); 75 | if(info) 76 | xInfo() << str; 77 | else 78 | xDebug() << str; 79 | } 80 | 81 | void InitCommand::tsPrepare(const QString &proFile, bool info) 82 | { 83 | QFile file(proFile); 84 | if(!file.exists()) 85 | throw tr("Target file \"%1\" does not exist").arg(proFile); 86 | if(!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) 87 | throw tr("Failed to open pro file \"%1\" with error: %2").arg(proFile, file.errorString()); 88 | 89 | QTextStream stream(&file); 90 | stream << "\nqpmxlrelease.target = lrelease\n" 91 | << "qpmxlrelease.CONFIG += recursive\n" 92 | << "qpmxlrelease.recurse_target = lrelease\n" 93 | << "QMAKE_EXTRA_TARGETS += qpmxlrelease\n"; 94 | stream.flush(); 95 | file.close(); 96 | 97 | auto str = tr("Successfully added lrelease target code to pro file \"%1\"").arg(proFile); 98 | if(info) 99 | xInfo() << str; 100 | else 101 | xDebug() << str; 102 | } 103 | 104 | void InitCommand::initialize(QCliParser &parser) 105 | { 106 | try { 107 | if(parser.isSet(QStringLiteral("qpmx-prepare"))) { 108 | prepare(parser.value(QStringLiteral("qpmx-prepare"))); 109 | qApp->quit(); 110 | return; 111 | } 112 | if(parser.isSet(QStringLiteral("ts-prepare"))) { 113 | tsPrepare(parser.value(QStringLiteral("ts-prepare"))); 114 | qApp->quit(); 115 | return; 116 | } 117 | 118 | auto reRun = parser.isSet(QStringLiteral("r")); 119 | 120 | if(parser.positionalArguments().size() != 2) { 121 | throw tr("Invalid arguments! You must specify the qmake path to use for compilation " 122 | "and the target directory to place the generated files in"); 123 | } 124 | auto qmake = parser.positionalArguments().value(0); 125 | auto outdir = parser.positionalArguments().value(1); 126 | 127 | //run install 128 | QStringList runArgs { 129 | QStringLiteral("install") 130 | }; 131 | if(reRun) 132 | runArgs.append(QStringLiteral("--renew")); 133 | subCall(runArgs); 134 | xDebug() << tr("Successfully ran install step"); 135 | 136 | //run compile 137 | runArgs = QStringList { 138 | QStringLiteral("compile"), 139 | QStringLiteral("--qmake"), 140 | qmake 141 | }; 142 | if(reRun) 143 | runArgs.append(QStringLiteral("--recompile")); 144 | if(parser.isSet(QStringLiteral("stderr"))) 145 | runArgs.append(QStringLiteral("--stderr")); 146 | if(parser.isSet(QStringLiteral("clean"))) 147 | runArgs.append(QStringLiteral("--clean")); 148 | subCall(runArgs); 149 | xDebug() << tr("Successfully ran compile step"); 150 | 151 | //run generate 152 | runArgs = QStringList { 153 | QStringLiteral("generate"), 154 | QStringLiteral("--qmake"), 155 | qmake, 156 | outdir 157 | }; 158 | if(reRun) 159 | runArgs.append(QStringLiteral("--recreate")); 160 | subCall(runArgs); 161 | xDebug() << tr("Successfully ran generate step"); 162 | 163 | xDebug() << tr("Completed qpmx initialization"); 164 | qApp->quit(); 165 | } catch (QString &s) { 166 | xCritical() << s; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /qpmx/qbs/qpmx.qbs: -------------------------------------------------------------------------------- 1 | import qbs 2 | import qbs.TextFile 3 | import qbs.File 4 | import qbs.FileInfo 5 | import qbs.Process 6 | import qbs.PathTools 7 | import "qpmx.js" as Qpmx 8 | 9 | Module { 10 | id: qpmxModule 11 | 12 | version: "%{version}" 13 | 14 | property string qpmxBin: "qpmx" 15 | property string qpmxDir: sourceDirectory 16 | readonly property string qpmxFile: FileInfo.joinPaths(qpmxDir, "qpmx.json") 17 | 18 | property string logLevel: "normal" 19 | PropertyOptions { 20 | name: "logLevel" 21 | description: "The log level the qpmx execution should use to output stuff" 22 | allowedValues: ["quiet", "warn-only", "normal", "verbose"] 23 | } 24 | property bool colors: true 25 | 26 | Depends { name: "qbs" } 27 | Depends { name: "Qt.core" } 28 | Depends { name: "qpmxdeps.global" } 29 | 30 | Depends { 31 | name: "qpmxdeps" 32 | condition: File.exists(qpmxFile) 33 | submodules: { 34 | var proc = new Process(); 35 | var args = Qpmx.setBaseArgs(["qbs", "load"], qpmxDir, logLevel, colors); 36 | proc.exec(qpmxBin, args, true); 37 | var subMods = proc.readStdOut().split("\n"); 38 | subMods.pop(); 39 | return subMods; 40 | } 41 | } 42 | 43 | FileTagger { 44 | patterns: ["qpmx.json"] 45 | fileTags: "qpmx-config" 46 | } 47 | 48 | FileTagger { 49 | patterns: ["qpmx.user.json"] 50 | fileTags: "qpmx-config-user" 51 | } 52 | 53 | Rule { 54 | multiplex: true 55 | condition: qpmxdeps.global.hooks.length > 0 || qpmxdeps.global.qrcs > 0 56 | requiresInputs: false 57 | outputFileTags: ["cpp", "qpmx-startup-hooks"] 58 | outputArtifacts: [ 59 | { 60 | filePath: "qpmx_startup_hooks.cpp", 61 | fileTags: ["cpp", "qpmx-startup-hooks"] 62 | } 63 | ] 64 | 65 | prepare: { 66 | var command = new Command(); 67 | command.description = "Generating qpmx startup hooks"; 68 | command.highlight = "codegen"; 69 | command.program = product.qpmx.qpmxBin; 70 | var arguments = Qpmx.setBaseArgs(["hook"], product.qpmx.qpmxDir, product.qpmx.logLevel, product.qpmx.colors); 71 | arguments.push("--out"); 72 | arguments.push(output.filePath); 73 | for(var i = 0; i < product.qpmxdeps.global.hooks.length; i++) 74 | arguments.push(product.qpmxdeps.global.hooks[i]); 75 | arguments.push("%%"); 76 | for(var i = 0; i < product.qpmxdeps.global.qrcs.length; i++) 77 | arguments.push(product.qpmxdeps.global.qrcs[i]); 78 | command.arguments = arguments; 79 | command.stdoutFilePath = product.qbs.nullDevice; 80 | return command; 81 | } 82 | } 83 | 84 | Rule { 85 | inputs: ["qpmx-ts"] 86 | 87 | Artifact { 88 | filePath: input.completeBaseName + ".qm-base" 89 | fileTags: ["qpmx-qm-base"] 90 | } 91 | 92 | prepare: { 93 | var inputFilePaths = [input.filePath]; 94 | var args = ["-silent", "-qm", output.filePath].concat(inputFilePaths); 95 | var cmd = new Command(product.Qt.core.binPath + "/" 96 | + product.Qt.core.lreleaseName, args); 97 | cmd.description = "Creating " + output.fileName; 98 | cmd.highlight = "filegen"; 99 | return cmd; 100 | } 101 | } 102 | 103 | Rule { 104 | inputs: ["qpmx-qm-base"] 105 | 106 | Artifact { 107 | filePath: FileInfo.joinPaths(product.Qt.core.qmDir, input.completeBaseName + ".qm") 108 | fileTags: ["qm", "qpmx-qm-merged"] 109 | } 110 | 111 | prepare: { 112 | var args = ["-if", "qm", "-i", input.filePath]; 113 | for(var i = 0; i < product.qpmxdeps.global.qmBaseFiles.length; i++) { 114 | var suffix = FileInfo.completeBaseName(product.qpmxdeps.global.qmBaseFiles[i]).split("_") 115 | suffix.shift(); 116 | while(suffix.length > 0) { 117 | if(input.baseName.endsWith(suffix.join("_"))) 118 | break; 119 | suffix.shift(); 120 | } 121 | if(suffix.length == 0) 122 | continue; 123 | 124 | args = args.concat(["-i", product.qpmxdeps.global.qmBaseFiles[i]]); 125 | } 126 | args = args.concat(["-of", "qm", "-o", output.filePath]) 127 | var cmd = new Command(product.Qt.core.binPath + "/lconvert", args); 128 | cmd.description = "Combining translations with qpmx package translations " + output.fileName; 129 | cmd.highlight = "filegen"; 130 | return cmd; 131 | } 132 | } 133 | 134 | Rule { 135 | inputs: [qbs.toolchain.contains("gcc") ? "qpmx-mri-script" : "staticlibrary"] 136 | 137 | Artifact { 138 | filePath: FileInfo.joinPaths("merged", input.completeBaseName + product.cpp.staticLibrarySuffix) 139 | fileTags: ["qpmx-staticlibrary-merged"] 140 | } 141 | 142 | prepare: { 143 | //create command 144 | var exec = ""; 145 | var args = []; 146 | if(product.qbs.toolchain.contains("msvc")) { 147 | exec = "lib.exe" 148 | args = ["/OUT:" + output.filePath, input.filePath] 149 | for(var i = 0; i < product.qpmxdeps.global.libdeps.length; i++) 150 | args.push(product.qpmxdeps.global.libdeps[i]); 151 | } else if(product.qbs.toolchain.contains("clang")) { 152 | exec = "libtool" 153 | args = ["-static", "-o", output.filePath, input.filePath] 154 | for(var i = 0; i < product.qpmxdeps.global.libdeps.length; i++) 155 | args.push(product.qpmxdeps.global.libdeps[i]); 156 | } else if(product.qbs.toolchain.contains("gcc")) { 157 | exec = "sh" 158 | args = ["-c", "ar -M < \"" + input.filePath + "\""] 159 | } 160 | var cmd = new Command(exec, args); 161 | cmd.description = "Merging libary with qpmx libraries"; 162 | cmd.highlight = "linker"; 163 | return cmd; 164 | } 165 | } 166 | 167 | Rule { 168 | inputs: ["staticlibrary"] 169 | condition: qbs.toolchain.contains("gcc") 170 | 171 | Artifact { 172 | filePath: FileInfo.joinPaths("merged", input.completeBaseName + ".mri") 173 | fileTags: ["qpmx-mri-script"] 174 | } 175 | 176 | prepare: { 177 | var cmd = new JavaScriptCommand(); 178 | cmd.description = "Generating mri script to merge libary with qpmx libraries"; 179 | cmd.highlight = "filegen"; 180 | cmd.sourceCode = function() { 181 | var file = new TextFile(output.filePath, TextFile.WriteOnly); 182 | file.writeLine("CREATE " + FileInfo.joinPaths(FileInfo.path(input.filePath), "merged", input.completeBaseName + product.cpp.staticLibrarySuffix)); 183 | file.writeLine("ADDLIB " + input.filePath); 184 | for(var i = 0; i < product.qpmxdeps.global.libdeps.length; i++) 185 | file.writeLine("ADDLIB " + product.qpmxdeps.global.libdeps[i]); 186 | file.writeLine("SAVE"); 187 | file.writeLine("END"); 188 | file.close(); 189 | }; 190 | return cmd; 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /qpmx/main.cpp: -------------------------------------------------------------------------------- 1 | #include "command.h" 2 | #include "qpmxformat.h" 3 | 4 | #include "listcommand.h" 5 | #include "searchcommand.h" 6 | #include "installcommand.h" 7 | #include "uninstallcommand.h" 8 | #include "updatecommand.h" 9 | #include "compilecommand.h" 10 | #include "generatecommand.h" 11 | #include "createcommand.h" 12 | #include "preparecommand.h" 13 | #include "publishcommand.h" 14 | #include "devcommand.h" 15 | #include "initcommand.h" 16 | #include "clearcachescommand.h" 17 | #include "translatecommand.h" 18 | #include "hookcommand.h" 19 | #include "qbscommand.h" 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | #include "bridge.h" 34 | using namespace qpmx; 35 | 36 | static bool colored = false; 37 | static QSet logLevel{QtCriticalMsg, QtFatalMsg}; 38 | 39 | template 40 | static void addCommand(QHash &commands); 41 | static void qpmxMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); 42 | 43 | int main(int argc, char *argv[]) 44 | { 45 | QCoreApplication a(argc, argv); 46 | QCoreApplication::setApplicationName(QStringLiteral(PROJECT_TARGET)); 47 | QCoreApplication::setApplicationVersion(QStringLiteral(VERSION)); 48 | QCoreApplication::setOrganizationName(QStringLiteral(COMPANY)); 49 | QCoreApplication::setOrganizationDomain(QStringLiteral(BUNDLE)); 50 | 51 | QJsonSerializer::registerAllConverters(); 52 | QJsonSerializer::registerAllConverters(); 53 | QJsonSerializer::registerAllConverters(); 54 | qRegisterMetaTypeStreamOperators(); 55 | 56 | QHash commands; 57 | addCommand(commands); 58 | addCommand(commands); 59 | addCommand(commands); 60 | addCommand(commands); 61 | addCommand(commands); 62 | addCommand(commands); 63 | addCommand(commands); 64 | addCommand(commands); 65 | addCommand(commands); 66 | addCommand(commands); 67 | addCommand(commands); 68 | addCommand(commands); 69 | addCommand(commands); 70 | addCommand(commands); 71 | addCommand(commands); 72 | addCommand(commands); 73 | 74 | QCliParser parser; 75 | Command::setupParser(parser, commands); 76 | parser.process(a, true); 77 | 78 | //setup logging 79 | QString prefix; 80 | if(parser.isSet(QStringLiteral("qmake-run"))) 81 | prefix = QStringLiteral("qpmx.json:1: "); 82 | #ifndef Q_OS_WIN 83 | colored = !parser.isSet(QStringLiteral("no-color")); 84 | if(colored) { 85 | qSetMessagePattern(QCoreApplication::translate("parser", "%{if-warning}\033[33m%1Warning: %{endif}" 86 | "%{if-critical}\033[31m%1Error: %{endif}" 87 | "%{if-fatal}\033[35m%1Fatal Error: %{endif}" 88 | "%{if-category}%{category}: %{endif}%{message}" 89 | "%{if-warning}\033[0m%{endif}" 90 | "%{if-critical}\033[0m%{endif}" 91 | "%{if-fatal}\033[0m%{endif}") 92 | .arg(prefix)); 93 | } else 94 | #endif 95 | { 96 | qSetMessagePattern(QCoreApplication::translate("parser", "%{if-warning}%1Warning: %{endif}" 97 | "%{if-critical}%1Error: %{endif}" 98 | "%{if-fatal}%1Fatal Error: %{endif}" 99 | "%{if-category}%{category}: %{endif}%{message}") 100 | .arg(prefix)); 101 | } 102 | 103 | if(!parser.isSet(QStringLiteral("quiet"))) { 104 | logLevel.insert(QtWarningMsg); 105 | logLevel.insert(QtInfoMsg); 106 | if(parser.isSet(QStringLiteral("verbose"))) 107 | logLevel.insert(QtDebugMsg); 108 | } else { 109 | if(parser.isSet(QStringLiteral("verbose"))) 110 | logLevel.insert(QtWarningMsg); 111 | } 112 | qInstallMessageHandler(qpmxMessageHandler); 113 | 114 | //perform cd 115 | if(parser.isSet(QStringLiteral("dir"))){ 116 | if(!QDir::setCurrent(parser.value(QStringLiteral("dir")))) { 117 | xCritical() << QCoreApplication::translate("parser", "Failed to enter working directory \"%1\"") 118 | .arg(parser.value(QStringLiteral("dir"))); 119 | return EXIT_FAILURE; 120 | } 121 | } 122 | 123 | Command *cmd = nullptr; 124 | for(auto it = commands.begin(); it != commands.end(); it++) { 125 | if(parser.enterContext(it.key())) { 126 | cmd = it.value(); 127 | break; 128 | } 129 | } 130 | if(!cmd) { 131 | Q_UNREACHABLE(); 132 | return EXIT_FAILURE; 133 | } 134 | 135 | QObject::connect(qApp, &QCoreApplication::aboutToQuit, 136 | cmd, &Command::fin); 137 | QCtrlSignalHandler::instance()->setAutoQuitActive(true); 138 | QTimer::singleShot(0, qApp, [&parser, cmd](){ 139 | QtCoroutine::createAndRun([&parser, cmd](){ 140 | qpmx::priv::QpmxBridge::registerInstance(new qpmx::priv::Bridge{cmd}); 141 | cmd->init(parser); 142 | parser.leaveContext(); 143 | }); 144 | }); 145 | return a.exec(); 146 | } 147 | 148 | template 149 | static void addCommand(QHash &commands) 150 | { 151 | auto cmd = new T(qApp); 152 | commands.insert(cmd->commandName(), cmd); 153 | } 154 | 155 | static void qpmxMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) 156 | { 157 | if(!logLevel.contains(type)) 158 | return; 159 | 160 | auto message = qFormatLogMessage(type, context, msg); 161 | if(colored) { 162 | message.replace(QStringLiteral("%{pkg}"), QStringLiteral("\033[36m")); 163 | message.replace(QStringLiteral("%{bld}"), QStringLiteral("\033[32m")); 164 | switch (type) { 165 | case QtDebugMsg: 166 | case QtInfoMsg: 167 | message.replace(QStringLiteral("%{end}"), QStringLiteral("\033[0m")); 168 | break; 169 | case QtWarningMsg: 170 | message.replace(QStringLiteral("%{end}"), QStringLiteral("\033[33m")); 171 | break; 172 | case QtCriticalMsg: 173 | message.replace(QStringLiteral("%{end}"), QStringLiteral("\033[31m")); 174 | break; 175 | case QtFatalMsg: 176 | message.replace(QStringLiteral("%{end}"), QStringLiteral("\033[35m")); 177 | break; 178 | default: 179 | Q_UNREACHABLE(); 180 | break; 181 | } 182 | } else { 183 | message.replace(QStringLiteral("%{pkg}"), QStringLiteral("\"")); 184 | message.replace(QStringLiteral("%{bld}"), QStringLiteral("\"")); 185 | message.replace(QStringLiteral("%{end}"), QStringLiteral("\"")); 186 | } 187 | 188 | if(type == QtDebugMsg || type == QtInfoMsg) 189 | std::cout << message.toStdString() << std::endl; 190 | else 191 | std::cerr << message.toStdString() << std::endl; 192 | 193 | if(type == QtCriticalMsg) 194 | qApp->exit(Command::exitCode()); 195 | } 196 | -------------------------------------------------------------------------------- /qpmx/qpmx_generated_base.pri: -------------------------------------------------------------------------------- 1 | #setup 2 | isEmpty(QPMX_WORKINGDIR):QPMX_WORKINGDIR = . 3 | debug_and_release { 4 | CONFIG(debug, debug|release): SUFFIX = /debug 5 | CONFIG(release, debug|release): SUFFIX = /release 6 | QPMX_WORKINGDIR = $$QPMX_WORKINGDIR$$SUFFIX 7 | } 8 | 9 | # libbuild detection 10 | PRE_TARGETDEPS += $$QPMX_LIB_DEPS #lib targetdeps, needed for private merge 11 | !qpmx_no_libbuild:equals(TEMPLATE, lib) { 12 | CONFIG += qpmx_as_private_lib 13 | CONFIG(static, static|shared):!isEmpty(QPMX_LIB_DEPS) { 14 | qpmx_lib_merge.target = qpmx_lib_merge 15 | win32:debug_and_release: QPMX_MERGE_TARGET = $(DESTDIR_TARGET) 16 | else:debug_and_release: QPMX_MERGE_TARGET = $(DESTDIR)$(TARGET) 17 | else: QPMX_MERGE_TARGET = $(TARGET) 18 | 19 | mac|ios|win32:!mingw { 20 | QPMX_RAW_TARGET = $${QPMX_MERGE_TARGET}.raw 21 | QPMX_LIB_DEPS += $$QPMX_RAW_TARGET 22 | qpmx_lib_merge.commands = $$QMAKE_MOVE $$QPMX_MERGE_TARGET $$QPMX_RAW_TARGET $$escape_expand(\\n\\t) 23 | mac|ios: qpmx_lib_merge.commands += libtool -static -o $$QPMX_MERGE_TARGET $$QPMX_LIB_DEPS $$escape_expand(\\n\\t) 24 | win32: qpmx_lib_merge.commands += lib.exe /OUT:$$QPMX_MERGE_TARGET $$QPMX_LIB_DEPS $$escape_expand(\\n\\t) 25 | qpmx_lib_merge.depends += "$$QPMX_MERGE_TARGET" 26 | } else { 27 | qpmx_lib_mri.target = $${QPMX_MERGE_TARGET}.mri 28 | qpmx_lib_mri.commands = echo "OPEN $${QPMX_MERGE_TARGET}" > $${QPMX_MERGE_TARGET}.mri $$escape_expand(\\n\\t) 29 | for(lib, QPMX_LIB_DEPS): qpmx_lib_mri.commands += echo "addlib $$lib" >> $${QPMX_MERGE_TARGET}.mri $$escape_expand(\\n\\t) 30 | qpmx_lib_mri.commands += echo "save" >> $${QPMX_MERGE_TARGET}.mri $$escape_expand(\\n\\t) 31 | qpmx_lib_mri.commands += echo "end" >> $${QPMX_MERGE_TARGET}.mri 32 | qpmx_lib_merge.commands += ar -M < "$${QPMX_MERGE_TARGET}.mri" $$escape_expand(\\n\\t) 33 | qpmx_lib_merge.depends += "$${QPMX_MERGE_TARGET}" qpmx_lib_mri 34 | QMAKE_EXTRA_TARGETS += qpmx_lib_mri 35 | 36 | qpmx_lib_mri_clean.target = qpmx_lib_mri_clean 37 | qpmx_lib_mri_clean.commands = $$QMAKE_DEL_FILE $${QPMX_MERGE_TARGET}.mri 38 | clean.depends += qpmx_lib_mri_clean 39 | QMAKE_EXTRA_TARGETS += qpmx_lib_mri_clean clean 40 | } 41 | 42 | qpmx_lib_merge.commands += echo "merged" > qpmx_lib_merge #prevents the target from beeing run multiple times 43 | qpmx_lib_merge_clean.target = qpmx_lib_merge_clean 44 | qpmx_lib_merge_clean.commands = $$QMAKE_DEL_FILE qpmx_lib_merge 45 | clean.depends += qpmx_lib_merge_clean 46 | 47 | all.depends += qpmx_lib_merge 48 | staticlib.depends += qpmx_lib_merge 49 | debug_and_release:!ReleaseBuild:!DebugBuild { 50 | qpmx_lib_merge_sub.target = qpmx_lib_merge 51 | qpmx_lib_merge_sub.CONFIG += recursive 52 | qpmx_lib_merge_sub.recurse_target = qpmx_lib_merge 53 | QMAKE_EXTRA_TARGETS += qpmx_lib_merge_sub 54 | } else: QMAKE_EXTRA_TARGETS += qpmx_lib_merge qpmx_lib_merge_clean all staticlib clean 55 | } 56 | } 57 | 58 | win32:!mingw: QPMX_SRC_SEPERATOR = %%%% 59 | else: QPMX_SRC_SEPERATOR = %% 60 | 61 | qpmx_src_build:CONFIG(static, static|shared): warning(qpmx source builds cannot generate a static library, as startup hooks and resources will not be available. Please switch to a compiled qpmx build!) 62 | 63 | #qpmx startup hook 64 | !qpmx_src_build:!isEmpty(QPMX_STARTUP_HOOKS)|!isEmpty(QPMX_RESOURCE_FILES) { 65 | qpmx_hook_target.target = "$$QPMX_WORKINGDIR/qpmx_startup_hooks.cpp" 66 | qpmx_hook_target.commands = $$QPMX_BIN hook $$QPMX_HOOK_EXTRA_OPTIONS --out $$shell_quote($$QPMX_WORKINGDIR/qpmx_startup_hooks.cpp) $$QPMX_STARTUP_HOOKS $$QPMX_SRC_SEPERATOR $$QPMX_RESOURCE_FILES 67 | qpmx_hook_target.depends += $$PWD/qpmx_generated.pri 68 | QMAKE_EXTRA_TARGETS += qpmx_hook_target 69 | GENERATED_SOURCES += "$$QPMX_WORKINGDIR/qpmx_startup_hooks.cpp" 70 | 71 | qpmx_hook_target_clean.target = qpmx-create-hooks-clean 72 | qpmx_hook_target_clean.commands = $$QMAKE_DEL_FILE $$shell_quote($$shell_path($$QPMX_WORKINGDIR/qpmx_startup_hooks.cpp)) 73 | clean.depends += qpmx_hook_target_clean 74 | QMAKE_EXTRA_TARGETS += qpmx_hook_target_clean clean 75 | } 76 | 77 | #translations 78 | QPMX_TRANSLATIONS += $$TRANSLATIONS #translations comming from the qpmx dependencies (src only) 79 | TRANSLATIONS = $$QPMX_TMP_TS 80 | isEmpty(QPMX_LRELEASE) { 81 | isEmpty(LRELEASE): qtPrepareTool(LRELEASE, lrelease) 82 | for(arg, LRELEASE): QPMX_LRELEASE += "+$${arg}" 83 | } 84 | !qpmx_src_build:isEmpty(QPMX_LCONVERT): qtPrepareTool(QPMX_LCONVERT, lconvert) 85 | 86 | qpmx_translate.name = $$QPMX_BIN translate ${QMAKE_FILE_IN} 87 | qpmx_translate.input = TRANSLATIONS 88 | qpmx_translate.variable_out = TRANSLATIONS_QM 89 | qpmx_translate.commands = $$QPMX_BIN translate $$QPMX_TRANSLATE_EXTRA_OPTIONS --outdir $$shell_quote($$QPMX_WORKINGDIR) --ts-file ${QMAKE_FILE_IN} 90 | qpmx_src_build: qpmx_translate.commands += --src $$QPMX_LRELEASE $$QPMX_SRC_SEPERATOR $$QPMX_TRANSLATIONS 91 | else: qpmx_translate.commands += --qmake $$shell_quote($$QMAKE_QMAKE) --lconvert $$QPMX_LCONVERT $$QPMX_LRELEASE $$QPMX_SRC_SEPERATOR $$QPMX_TS_DIRS 92 | qpmx_translate.output = $$QPMX_WORKINGDIR/${QMAKE_FILE_BASE}.qm 93 | qpmx_translate.clean += $$QPMX_WORKINGDIR/${QMAKE_FILE_BASE}.qm $$QPMX_WORKINGDIR/${QMAKE_FILE_BASE}.qm-base 94 | qpmx_translate.CONFIG += no_link 95 | QMAKE_EXTRA_COMPILERS += qpmx_translate 96 | 97 | qpmx_extra_translate.name = $$LRELEASE translate ${QMAKE_FILE_IN} 98 | qpmx_extra_translate.input = EXTRA_TRANSLATIONS 99 | qpmx_extra_translate.variable_out = TRANSLATIONS_QM 100 | qpmx_extra_translate.commands = $$LRELEASE ${QMAKE_FILE_IN} -qm ${QMAKE_FILE_OUT} 101 | qpmx_extra_translate.output = $$QPMX_WORKINGDIR/${QMAKE_FILE_BASE}.qm 102 | qpmx_extra_translate.CONFIG += no_link 103 | QMAKE_EXTRA_COMPILERS += qpmx_extra_translate 104 | 105 | lrelease_target.target = lrelease 106 | debug_and_release:!ReleaseBuild:!DebugBuild: { 107 | lrelease_sub_target.target = lrelease-subtarget 108 | lrelease_sub_target.CONFIG += recursive 109 | lrelease_sub_target.recurse_target = lrelease 110 | QMAKE_EXTRA_TARGETS += lrelease_sub_target 111 | 112 | CONFIG(debug, debug|release): lrelease_target.depends += debug-lrelease_sub_target 113 | CONFIG(release, debug|release): lrelease_target.depends += release-lrelease_sub_target 114 | } else: lrelease_target.depends += compiler_qpmx_translate_make_all compiler_qpmx_extra_translate_make_all 115 | lrelease_target.commands = 116 | QMAKE_EXTRA_TARGETS += lrelease_target 117 | 118 | qpmx_ts_target.CONFIG += no_check_exist 119 | #qpmx_ts_target.files = $$TRANSLATIONS_QM 120 | for(tsfile, TRANSLATIONS) { 121 | tsBase = $$basename(tsfile) 122 | qpmx_ts_target.files += "$$OUT_PWD/$$QPMX_WORKINGDIR/$$replace(tsBase, \.ts, .qm)" 123 | } 124 | for(tsfile, EXTRA_TRANSLATIONS) { 125 | tsBase = $$basename(tsfile) 126 | qpmx_ts_target.files += "$$OUT_PWD/$$QPMX_WORKINGDIR/$$replace(tsBase, \.ts, .qm)" 127 | } 128 | 129 | QMAKE_DIR_REPLACE += QPMX_WORKINGDIR 130 | QMAKE_DIR_REPLACE_SANE += QPMX_WORKINGDIR 131 | -------------------------------------------------------------------------------- /qpmx/translatecommand.cpp: -------------------------------------------------------------------------------- 1 | #include "translatecommand.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | using namespace qpmx; 9 | 10 | TranslateCommand::TranslateCommand(QObject *parent) : 11 | Command{parent} 12 | {} 13 | 14 | QString TranslateCommand::commandName() const 15 | { 16 | return QStringLiteral("translate"); 17 | } 18 | 19 | QString TranslateCommand::commandDescription() const 20 | { 21 | return tr("Prepare translations by compiling the projects translations and combining " 22 | "it with the translations of qpmx packages"); 23 | } 24 | 25 | QSharedPointer TranslateCommand::createCliNode() const 26 | { 27 | auto translateNode = QSharedPointer::create(); 28 | translateNode->setHidden(true); 29 | translateNode->addOption({ 30 | QStringLiteral("src"), 31 | tr("Assume a source build, and create translations for a source build instead of binary.") 32 | }); 33 | translateNode->addOption({ 34 | QStringLiteral("lconvert"), 35 | tr("The to the lconvert binary to be used (binary builds only)."), 36 | tr("path") 37 | }); 38 | translateNode->addOption({ 39 | {QStringLiteral("m"), QStringLiteral("qmake")}, 40 | tr("The version to use to find the corresponding translations. If not specified " 41 | "the qmake from path is used (binary builds only)."), 42 | tr("qmake"), 43 | QStandardPaths::findExecutable(QStringLiteral("qmake")) 44 | }); 45 | translateNode->addOption({ 46 | QStringLiteral("outdir"), 47 | tr("The to generate the translation files in. If not passed, the current directoy is used."), 48 | tr("directory") 49 | }); 50 | translateNode->addOption({ 51 | QStringLiteral("ts-file"), 52 | tr("The ts to translate and combine with the qpmx translations (required)."), 53 | tr("file") 54 | }); 55 | translateNode->addPositionalArgument(QStringLiteral("lrelease"), 56 | tr("The path to the lrelease binary, as well as additional arguments.\n" 57 | "IMPORTANT: Extra arguments like \"-nounfinished\" must NOT be specified with the " 58 | "leading \"-\"! Instead, use a \"+\". It is replaced internally. Example: \"+nounfinished\"."), 59 | QStringLiteral(" [ ...]")); 60 | translateNode->addPositionalArgument(QStringLiteral("qpmx-translations"), 61 | tr("The ts-files to combine with the specified translations. " 62 | "Typically, the QPMX_TRANSLATIONS qmake variable is passed (source builds only)."), 63 | QStringLiteral("[%% ...]")); 64 | translateNode->addPositionalArgument(QStringLiteral("qpmx-translations-dirs"), 65 | tr("The directories containing qm-files to combine with the specified translations. " 66 | "Typically, the QPMX_TS_DIRS qmake variable is passed (binary builds only)."), 67 | QStringLiteral("| [%% ...]")); 68 | return translateNode; 69 | } 70 | 71 | void TranslateCommand::initialize(QCliParser &parser) 72 | { 73 | try { 74 | if(parser.isSet(QStringLiteral("outdir"))) 75 | _outDir = parser.value(QStringLiteral("outdir")) + QLatin1Char('/'); 76 | else 77 | _outDir = QStringLiteral("./"); 78 | _qmake = parser.value(QStringLiteral("qmake")); 79 | _lconvert = parser.value(QStringLiteral("lconvert")); 80 | _tsFile = parser.value(QStringLiteral("ts-file")); 81 | if(!QFile::exists(_tsFile)) 82 | throw tr("You must specify the --ts-file option with a valid file!"); 83 | 84 | auto pArgs = parser.positionalArguments(); 85 | if(pArgs.isEmpty()) 86 | throw tr("You must specify the path to the lrelease binary after the qpmx file as argument, with possible additional arguments"); 87 | do { 88 | auto arg = pArgs.takeFirst(); 89 | if(arg == QStringLiteral("%%")) { 90 | _qpmxTsFiles = pArgs; 91 | xDebug() << tr("Extracted qpmx translation files/dirs as: %1").arg(_qpmxTsFiles.join(tr(", "))); 92 | break; 93 | } 94 | if(arg[0] == QLatin1Char('+')) 95 | _lrelease.append(arg.mid(1)); 96 | else 97 | _lrelease.append(arg); 98 | } while(!pArgs.isEmpty()); 99 | xDebug() << tr("Extracted lrelease as: %1").arg(_lrelease.join(QStringLiteral(" "))); 100 | 101 | if(parser.isSet(QStringLiteral("src"))) 102 | srcTranslate(); 103 | else 104 | binTranslate(); 105 | 106 | qApp->quit(); 107 | } catch(QString &s) { 108 | xCritical() << s; 109 | } 110 | } 111 | 112 | void TranslateCommand::binTranslate() 113 | { 114 | if(!QFile::exists(_qmake)) 115 | throw tr("Choosen qmake executable \"%1\" does not exist").arg(_qmake); 116 | 117 | //first: translate the ts file 118 | QFileInfo tsInfo(_tsFile); 119 | QString qmFile = _outDir + tsInfo.completeBaseName() + QStringLiteral(".qm"); 120 | QString qmBaseFile = _outDir + tsInfo.completeBaseName() + QStringLiteral(".qm-base"); 121 | 122 | auto args = _lrelease; 123 | args.append({_tsFile, QStringLiteral("-qm"), qmBaseFile}); 124 | execute(args); 125 | 126 | //now combine them into one 127 | auto locale = localeString(); 128 | if(locale.isNull()) { 129 | QFile::rename(qmBaseFile, qmFile); 130 | return; 131 | } 132 | 133 | args = QStringList { 134 | _lconvert, 135 | QStringLiteral("-if"), QStringLiteral("qm"), 136 | QStringLiteral("-i"), qmBaseFile 137 | }; 138 | 139 | for(const auto &tsDir : qAsConst(_qpmxTsFiles)) { 140 | QDir bDir(tsDir); 141 | if(!bDir.exists()) { 142 | xWarning() << tr("Translation directory does not exist: %1").arg(tsDir); 143 | continue; 144 | } 145 | 146 | bDir.setFilter(QDir::Files | QDir::Readable); 147 | bDir.setNameFilters({QStringLiteral("*.qm")}); 148 | for(const auto &qpmxQmFile : bDir.entryInfoList()) { 149 | auto baseName = qpmxQmFile.completeBaseName(); 150 | if(baseName.endsWith(locale)) 151 | args.append({QStringLiteral("-i"), qpmxQmFile.absoluteFilePath()}); 152 | } 153 | } 154 | 155 | args.append({QStringLiteral("-of"), QStringLiteral("qm")}); 156 | args.append({QStringLiteral("-o"), qmFile}); 157 | execute(args); 158 | } 159 | 160 | void TranslateCommand::srcTranslate() 161 | { 162 | QFileInfo tsInfo(_tsFile); 163 | QString qmFile = _outDir + tsInfo.completeBaseName() + QStringLiteral(".qm"); 164 | 165 | QStringList tsFiles(_tsFile); 166 | auto locale = localeString(); 167 | if(!locale.isNull()) { 168 | //collect all possible qpmx ts files 169 | for(const auto &ts : qAsConst(_qpmxTsFiles)) { 170 | auto baseName = QFileInfo(ts).completeBaseName(); 171 | if(baseName.endsWith(locale)) 172 | tsFiles.append(ts); 173 | } 174 | } 175 | 176 | auto args = _lrelease; 177 | args.append(tsFiles); 178 | args.append({QStringLiteral("-qm"), qmFile}); 179 | execute(args); 180 | } 181 | 182 | void TranslateCommand::execute(QStringList command) 183 | { 184 | xDebug() << tr("Running subcommand: %1").arg(command.join(QLatin1Char(' '))); 185 | 186 | QProcess proc; 187 | proc.setProgram(command.takeFirst()); 188 | proc.setArguments(command); 189 | proc.setProcessChannelMode(QProcess::ForwardedChannels); 190 | auto res = QtCoroutine::await(&proc); 191 | 192 | switch (res) { 193 | case -1://crashed 194 | throw tr("Failed to run \"%1\" to compile \"%2\" with error: %3") 195 | .arg(proc.program(), _tsFile, proc.errorString()); 196 | case 0://success 197 | xDebug() << tr("Successfully ran \"%1\" to compile \"%2\"") 198 | .arg(proc.program(), _tsFile); 199 | break; 200 | default: 201 | throw tr("Running \"%1\" to compile \"%2\" failed with exit code: %3") 202 | .arg(proc.program(), _tsFile) 203 | .arg(res); 204 | } 205 | } 206 | 207 | QString TranslateCommand::localeString() 208 | { 209 | auto parts = QFileInfo(_tsFile).baseName().split(QLatin1Char('_')); 210 | do { 211 | auto nName = parts.join(QLatin1Char('_')); 212 | QLocale locale(nName); 213 | if(locale != QLocale::c()) 214 | return QLatin1Char('_') + nName; 215 | parts.removeFirst(); 216 | } while(!parts.isEmpty()); 217 | 218 | xWarning() << tr("Unable to detect locale of file \"%1\". Translation combination is skipped") 219 | .arg(_tsFile); 220 | return {}; 221 | } 222 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [DEPRECATED] qpmx 2 | The Advanced Qt package manager - a frontend/replacement for qpm. 3 | 4 | > This project has been deprecated in favor of [qdep](https://github.com/Skycoder42/qdep). Please migrate all you packages and project to qdep as qpmx will not be maintained anymore. 5 | 6 | ---------------------- 7 | 8 | [![Travis Build Status](https://travis-ci.org/Skycoder42/qpmx.svg?branch=master)](https://travis-ci.org/Skycoder42/qpmx) 9 | [![Appveyor Build status](https://ci.appveyor.com/api/projects/status/xve94rbg8ewsg59s?svg=true)](https://ci.appveyor.com/project/Skycoder42/qpmx) 10 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/99993552c83f4fa18288620231b9a1fa)](https://www.codacy.com/app/Skycoder42/qpmx?utm_source=github.com&utm_medium=referral&utm_content=Skycoder42/qpmx&utm_campaign=Badge_Grade) 11 | [![AUR](https://img.shields.io/aur/version/qpmx.svg)](https://aur.archlinux.org/packages/qpmx/) 12 | 13 | ## Features 14 | qpmx is designed as a package manager tool without any backend. It is an advanced tool with support for qpm and git package repositories, and provides a bunch of features to make the usage as easy as possible. The main features ares: 15 | 16 | - global source caching, to reduce downloads 17 | - precompiles packages locally to speed up builds 18 | - but also supports build with sources directly 19 | - fully cross-platform 20 | - can be run on any "host" platform supported by Qt 21 | - can compile packages for any Qt platform 22 | - easy and simple qmake integration, with just two additional lines a the pro file 23 | - automatically added by qpmx on package installation 24 | - Supports translations and local includes for (static) library projects 25 | - Resources and `Q_COREAPP_STARTUP_FUNCTION` work as expected, even when used as compiled library 26 | - can search packages 27 | - Methods for developers: 28 | - Use a local copy instead of an actual package for debug purpose 29 | - still supports source and compile builds 30 | - provide methods to publish packages directly via qpmx 31 | 32 | ### Backends 33 | qpmx is a tool only, and thus needs external "backends" that provide it with packages. This is done via a simple plugin. Currently, two plugins are provided: git and qpm 34 | 35 | #### QPM plugin 36 | With the qpm plugin, qpmx can be used to install qpm packages, without loosing all the advantages like caching. It properly resolves dependencies, can search and is version-aware. It requires you to have qpm installed as well. Because qpm packages are not designed to be precompiled, there migth be issues with some packages. In case you encounter those, try switching to source mode. 37 | 38 | #### GIT plugin 39 | The git/github plugin supports any git repository. The urls are the package names, and the tags the versions. Tags must be real versions (e.g. `1.0.1`). It cannot search, and may not support all url schemes. It also comes with a github provider, which can be used to have simpler names for github projects. 40 | 41 | ## Installation 42 | - **Arch-Users:** [qpmx](https://aur.archlinux.org/packages/qpmx/), [qpmx-gitsource](https://aur.archlinux.org/packages/qpmx-gitsource/), [qpmx-qpmsource](https://aur.archlinux.org/packages/qpmx-qpmsource/) 43 | - **Ubuntu:** Launchpad-PPA: 44 | - Bionic: [ppa:skycoder42/qt-modules](https://launchpad.net/~skycoder42/+archive/ubuntu/qt-modules), package `qpmx` 45 | - **MacOs:** 46 | - Tap: [`brew tap Skycoder42/qt-modules`](https://github.com/Skycoder42/homebrew-qt-modules) 47 | - Package: `qpmx` 48 | - Compile yourself. You will need [QtJsonSerializer](https://github.com/Skycoder42/QJsonSerializer) 49 | - Download from the releases package 50 | 51 | ## Examples 52 | Have a look at https://github.com/Skycoder42/qpmx-sample-package. Its a project with a sample qpmx packages, as well as an application (https://github.com/Skycoder42/qpmx-sample-package/tree/master/qpmx-test) that uses this sample package. It's a git/github package with dependencies to qpm packages, to show off the possibilities. 53 | 54 | ### General usage 55 | Simply install packages using `qpmx install` (If not done automatically, prepare the pro file with `qpm init --prepare `). And thats it. Simply run qmake, and qpmx will automatically install, precompile and prepare 56 | packages, and include everything required to your pro file automatically. 57 | 58 | #### Translations 59 | To have translations properly working, you can use the `TRANSLATIONS` variable in both, qpmx packages and in your final project. Simply run `make lrelease` and translations will be compiled automatically. The same happens with files in `EXTRA_TRANSLATIONS`, but without joining them with qpmx package translations. You can use that one if you have more the 1 ts file per language. To install them, use the prepared target: 60 | 61 | ```pro 62 | qpmx_ts_target.path = $$[QT_INSTALL_TRANSLATIONS] # or wherever you want to install them to 63 | INSTALLS += qpmx_ts_target 64 | ``` 65 | 66 | ### Common use cases 67 | #### Package Users 68 | - Installing a package: `qpmx install [::][@]` 69 | Example: `qpmx install com.github.skycoder42.qpmx-sample-package` would search all providers for the package and then install the latest version. 70 | - Preparing a pro-file to include qpmx packages: `qpmx init --prepare ` 71 | This is done automatically on the first install, but if you are missing the line, you can add it this way. 72 | - Search for a package `qpmx search de.skycoder42.qtmvvm` 73 | Will search all providers that support searching (qpm) for packages that match the given name. 74 | 75 | #### Package Developers 76 | - Create a qpmx-file for a package: `qpmx create --prepare qpm` 77 | This will create/update a qpmx-file based of your inputs, and in this example, prepare it for publishing with the given provider. 78 | - Publish a qpmx package: `qpmx publish 4.2.0` 79 | Publishes the package for all providers it was prepared for, with the given version of 4.2.0 80 | - Switch a package dependency to a local path (dev mode): `qpmx dev add ::@ ` 81 | This will use the local `` as package source instead of downloading it from git/qpm/... (of course only for this qpmx.json project) 82 | 83 | ## Special (qmake) stuff 84 | ### qmake variables 85 | Variable | Description 86 | --------------------------------|------------- 87 | QPMX_EXTRA_OPTIONS | Additional option parameters for the `qpmx init` invocation 88 | QPMX_TRANSLATE_EXTRA_OPTIONS | Additional option parameters for the `qpmx translate` invocation 89 | QPMX_HOOK_EXTRA_OPTIONS | Additional option parameters for the `qpmx hook` invocation 90 | PUBLIC_HEADERS | *qpmx package only:* The headers to be used by users. If left empty, `HEADERS` is used 91 | QPMX_WORKINGDIR | The (sub)directory to use for generation of qpmx files. If left empty, the build directory is used 92 | EXTRA_TRANSLATIONS | Just like `TRANSLATIONS`, but qpmx will not join those files with the qpmx translations (but still compile) 93 | QPMX_INSTALL_DIR | A special variable set for prc files included in static qpmx usage. Contains the path where the binaries etc. have been installed to 94 | 95 | ### Extra targets 96 | Target | Description 97 | ----------------|------------- 98 | qpmx_ts_target | A target to install compiled translations (`.qm`) files. Use like the `target` target (See https://doc.qt.io/qt-5/qmake-advanced-usage.html#installing-files) 99 | 100 | ### Special CONFIG values 101 | Value | Description 102 | --------------------|------------- 103 | qpmx_static | *qpmx package only:* Is defined when a qpmx package is build as static library 104 | qpmx_src_build | *qpmx package only:* Is defined when a qpmx package is included as source package into a project 105 | qpmx_no_libbuild | Disable auto-detection of library builds. See section below 106 | 107 | **Note:** If neither `qpmx_static` nor `qpmx_src_build` are defined, the package is used as static library in a project (typically, in your prc files) 108 | 109 | #### QPMX libbuilds 110 | In order to make it possible to use qpmx to create static and shared libraries that depend on qpmx packages but want to keep that dependency "internal", 111 | qpmx by default does 2 things to make this possible. Both can be disabled by adding `qpmx_no_libbuild` to the config. 112 | 113 | 1. Private libs: The qpmx libraries are linked against as "private" libraries, effectively hiding them from for example la or prl files. 114 | 2. Static library merging: Generated static libraries are merged with the compiled qpmx packages into one library that can be easily deployed. 115 | 116 | ### Environment variables 117 | Variable | Description 118 | ----------------|------------- 119 | QPMX_CACHE_DIR | The directory to use as to cache qpmx stuff to. If not set or empty, QStandardPaths::CacheLocation is used. 120 | 121 | 122 | ## Documentation 123 | Planned for the future. You can run `qpmx --help` and `qpmx --help` to see what the tool can do. it's mostly non-interactive, but a few commands do require user interaction. 124 | -------------------------------------------------------------------------------- /qpmx/generatecommand.cpp: -------------------------------------------------------------------------------- 1 | #include "compilecommand.h" 2 | #include "generatecommand.h" 3 | 4 | #include 5 | using namespace qpmx; 6 | 7 | GenerateCommand::GenerateCommand(QObject *parent) : 8 | Command(parent), 9 | _genFile(nullptr), 10 | _qmake() 11 | {} 12 | 13 | QString GenerateCommand::commandName() const 14 | { 15 | return QStringLiteral("generate"); 16 | } 17 | 18 | QString GenerateCommand::commandDescription() const 19 | { 20 | return tr("Generate the qpmx_generated.pri, internally used to include compiled packages."); 21 | } 22 | 23 | QSharedPointer GenerateCommand::createCliNode() const 24 | { 25 | auto generateNode = QSharedPointer::create(); 26 | generateNode->addPositionalArgument(QStringLiteral("outdir"), 27 | tr("The directory to generate the file in.")); 28 | generateNode->addOption({ 29 | {QStringLiteral("m"), QStringLiteral("qmake")}, 30 | tr("The version to include compiled binaries for. If not specified " 31 | "the qmake from path is be used."), 32 | tr("qmake"), 33 | QStandardPaths::findExecutable(QStringLiteral("qmake")) 34 | }); 35 | generateNode->addOption({ 36 | {QStringLiteral("r"), QStringLiteral("recreate")}, 37 | tr("Always delete and recreate the file if it exists, not only when the configuration changed."), 38 | }); 39 | return generateNode; 40 | } 41 | 42 | void GenerateCommand::initialize(QCliParser &parser) 43 | { 44 | try { 45 | if(parser.positionalArguments().size() != 1) 46 | throw tr("Invalid arguments! You must specify the target directory as a single parameter"); 47 | 48 | QDir tDir(parser.positionalArguments().value(0)); 49 | if(!tDir.mkpath(QStringLiteral("."))) 50 | throw tr("Failed to create target directory"); 51 | _genFile = new QFile(tDir.absoluteFilePath(QStringLiteral("qpmx_generated.pri")), this); 52 | auto cachePath = tDir.absoluteFilePath(QStringLiteral(".qpmx.cache")); 53 | 54 | //qmake kit 55 | _qmake = parser.value(QStringLiteral("qmake")); 56 | if(!QFile::exists(_qmake)) 57 | throw tr("Choosen qmake executable \"%1\" does not exist").arg(_qmake); 58 | 59 | auto mainFormat = QpmxUserFormat::readDefault(true); 60 | if(_genFile->exists()) { 61 | if(!parser.isSet(QStringLiteral("recreate"))) { 62 | auto cacheFormat = QpmxCacheFormat::readCached(tDir); 63 | if(!hasChanged(mainFormat, cacheFormat)) { 64 | xDebug() << tr("Unchanged configuration. Skipping generation"); 65 | qApp->quit(); 66 | return; 67 | } 68 | } 69 | 70 | if(!_genFile->remove()) 71 | throw tr("Failed to remove qpmx_generated.pri with error: %1").arg(_genFile->errorString()); 72 | if(QFile::exists(cachePath) && !QFile::remove(cachePath)) 73 | throw tr("Failed to remove qpmx cache file"); 74 | } 75 | 76 | //create the file 77 | xInfo() << tr("Updating qpmx_generated.pri to apply changes"); 78 | if(mainFormat.hasDevOptions()) 79 | setDevMode(true); 80 | createPriFile(mainFormat); 81 | if(!QpmxCacheFormat::writeCached(tDir, cachedFormat(mainFormat))) 82 | xWarning() << tr("Failed to cache qpmx.json file. This means generate will always recreate the qpmx_generated.pri"); 83 | 84 | xDebug() << tr("Pri-File generation completed"); 85 | qApp->quit(); 86 | } catch (QString &s) { 87 | xCritical() << s; 88 | } 89 | } 90 | 91 | Command::BuildId GenerateCommand::kitId(const QpmxUserFormat &format) const 92 | { 93 | if(format.source) 94 | return QStringLiteral("src"); 95 | else 96 | return QtKitInfo::findKitId(buildDir(), _qmake); 97 | } 98 | 99 | QpmxCacheFormat GenerateCommand::cachedFormat(const QpmxUserFormat &format) const 100 | { 101 | return {format, kitId(format)}; 102 | } 103 | 104 | bool GenerateCommand::hasChanged(const QpmxUserFormat ¤tUser, const QpmxCacheFormat &cache) 105 | { 106 | auto current = cachedFormat(currentUser); 107 | 108 | if(current.buildKit != cache.buildKit || 109 | current.source != cache.source || 110 | current.prcFile != cache.prcFile || 111 | current.priIncludes != cache.priIncludes || 112 | current.dependencies.size() != cache.dependencies.size()) 113 | return true; 114 | 115 | auto cCache = cache.dependencies; 116 | for(const auto &dep : qAsConst(current.dependencies)) { 117 | auto cIdx = cCache.indexOf(dep); 118 | if(cIdx == -1) 119 | return true; 120 | if(dep.version != cCache.takeAt(cIdx).version) 121 | return true; 122 | } 123 | if(!cCache.isEmpty()) 124 | return true; 125 | 126 | auto dCache = cache.devDependencies; 127 | for(const auto &dep : qAsConst(current.devDependencies)) { 128 | auto cIdx = dCache.indexOf(dep); 129 | if(cIdx == -1) 130 | return true; 131 | auto dDep = dCache.takeAt(cIdx); 132 | if(dep.version != dDep.version || 133 | dep.path != dDep.path) 134 | return true; 135 | } 136 | if(!dCache.isEmpty()) 137 | return true; 138 | 139 | auto aCache = cache.devAliases; 140 | for(const auto &alias : qAsConst(current.devAliases)) { 141 | auto cIdx = aCache.indexOf(alias); 142 | if(cIdx == -1) 143 | return true; 144 | auto cAlias = aCache.takeAt(cIdx); 145 | if(cAlias.alias != alias.alias || 146 | cAlias.alias.version != alias.alias.version) 147 | return true; 148 | } 149 | if(!aCache.isEmpty()) 150 | return true; 151 | 152 | return false; 153 | } 154 | 155 | void GenerateCommand::createPriFile(const QpmxUserFormat ¤t) 156 | { 157 | if(!_genFile->open(QIODevice::WriteOnly | QIODevice::Text)) 158 | throw tr("Failed to open qpmx_generated.pri with error: %1").arg(_genFile->errorString()); 159 | 160 | //create & prepare 161 | QTextStream stream(_genFile); 162 | stream << "!qpmx_sub_pri {\n" 163 | << "\tQPMX_TMP_TS = $$TRANSLATIONS\n" 164 | << "\tTRANSLATIONS = \n" 165 | << "\tQPMX_BIN = \"" << QDir::toNativeSeparators(QCoreApplication::applicationFilePath()) << "\"\n"; 166 | if(current.source) 167 | stream << "\tCONFIG += qpmx_src_build\n"; 168 | else { 169 | stream << "\tQPMX_APP_LIBS = $$LIBS\n" 170 | << "\tLIBS =\n" 171 | << "\tgcc:!mac: LIBS += -Wl,--start-group\n"; 172 | } 173 | stream << "}\n\n"; 174 | 175 | //add possible includes 176 | stream << "#local qpmx pri includes\n"; 177 | if(current.source) {//not supported 178 | if(!current.priIncludes.isEmpty()) { 179 | stream << "warning(pri includes for sources builds will NOT load startup hooks or resources. " 180 | << "Make shure to link the referenced library with ALL defined symbols!)\n" 181 | << "win32: warning(See https://docs.microsoft.com/de-de/cpp/build/reference/wholearchive-include-all-library-object-files)\n" 182 | << "else: warning(See https://stackoverflow.com/a/14116513)\n"; 183 | for(const auto &inc : current.priIncludes) { 184 | stream << "INCLUDEPATH += $$fromfile(" << inc << "/qpmx_generated.pri, INCLUDEPATH)\n" 185 | << "QPMX_INCLUDE_GUARDS += $$fromfile(" << inc << "/qpmx_generated.pri, QPMX_INCLUDE_GUARDS)\n"; 186 | } 187 | } 188 | } else { 189 | stream << "!qpmx_sub_pri {\n" 190 | << "\tCONFIG += qpmx_sub_pri\n"; 191 | for(const auto &inc : current.priIncludes) 192 | stream << "\tinclude(" << inc << "/qpmx_generated.pri)\n"; 193 | stream << "\tCONFIG -= qpmx_sub_pri\n" 194 | << "} else {\n"; 195 | for(const auto &inc : current.priIncludes) 196 | stream << "\tinclude(" << inc << "/qpmx_generated.pri)\n"; 197 | stream << "}\n"; 198 | } 199 | 200 | //add dependencies 201 | stream << "\n#dependencies\n" 202 | << "QPMX_TS_DIRS = \n"; //clean for only use local deps 203 | for(const auto &dep : current.allDeps()) { 204 | auto dir = buildDir(kitId(current), dep.pkg()); 205 | stream << "include(" << dir.absoluteFilePath(QStringLiteral("include.pri")) << ")\n"; 206 | } 207 | 208 | //top-level pri only 209 | stream << "\n!qpmx_sub_pri {\n"; 210 | 211 | if(!current.devDependencies.isEmpty()) { 212 | //add "dummy" dev deps, for easier access 213 | stream << "\tCONFIG -= qpmx_never_ever_ever_true\n" 214 | << "\tqpmx_never_ever_ever_true { #trick to make QtCreator show dev dependencies in the build tree\n"; 215 | for(const auto &dep : current.devDependencies) { 216 | auto depDir = QDir::current(); 217 | if(!depDir.cd(dep.path)) { 218 | xWarning() << tr("Unabled to find directory of dev dependency %1").arg(dep.toString()); 219 | continue; 220 | } 221 | 222 | auto format = QpmxFormat::readFile(depDir); 223 | if(!format.priFile.isEmpty()) 224 | stream << "\t\tinclude(" << depDir.absoluteFilePath(format.priFile) << ")\n"; 225 | } 226 | stream << "\t}\n\n"; 227 | } 228 | 229 | //add fixed part 230 | QFile tCmp(QStringLiteral(":/build/qpmx_generated_base.pri")); 231 | if(!tCmp.open(QIODevice::ReadOnly | QIODevice::Text)) 232 | throw tr("Failed to load translation compiler cached code with error: %1").arg(tCmp.errorString()); 233 | while(!tCmp.atEnd()) 234 | stream << "\t" << tCmp.readLine(); 235 | tCmp.close(); 236 | 237 | //final 238 | if(!current.source) { 239 | stream << "\n\tgcc:!mac: LIBS += -Wl,--end-group\n" 240 | << "\tqpmx_as_private_lib: LIBS_PRIVATE += $$LIBS\n" 241 | << "\telse: QPMX_APP_LIBS += $$LIBS\n" 242 | << "\tLIBS = $$QPMX_APP_LIBS\n"; 243 | } 244 | stream << "}\n"; 245 | stream.flush(); 246 | _genFile->close(); 247 | } 248 | -------------------------------------------------------------------------------- /qpmx/devcommand.cpp: -------------------------------------------------------------------------------- 1 | #include "devcommand.h" 2 | using namespace qpmx; 3 | 4 | DevCommand::DevCommand(QObject *parent) : 5 | Command(parent) 6 | {} 7 | 8 | QString DevCommand::commandName() const 9 | { 10 | return QStringLiteral("dev"); 11 | } 12 | 13 | QString DevCommand::commandDescription() const 14 | { 15 | return tr("Switch packages from or to developer mode."); 16 | } 17 | 18 | QSharedPointer DevCommand::createCliNode() const 19 | { 20 | /*MAJOR further group commands: 21 | * dev 22 | * dep 23 | * add 24 | * remove 25 | * commit 26 | * alias 27 | * add 28 | * remove 29 | */ 30 | auto devNode = QSharedPointer::create(); 31 | auto devAddNode = devNode->addLeafNode(QStringLiteral("add"), 32 | tr("Add a packages as \"dev package\" to the qpmx.json.user file.")); 33 | devAddNode->addPositionalArgument(QStringLiteral("package"), 34 | tr("The package(s) to add as dev package"), 35 | QStringLiteral("[::@")); 36 | devAddNode->addPositionalArgument(QStringLiteral("pri-path"), 37 | tr("The local path to the pri file to be used to replace the preceeding package with."), 38 | QStringLiteral(" ...]")); 39 | 40 | auto devRemoveNode = devNode->addLeafNode(QStringLiteral("remove"), 41 | tr("Remove a \"dev package\" (or alias) from the qpmx.json.user file.")); 42 | devRemoveNode->addOption({ 43 | QStringLiteral("alias"), 44 | tr("Remove an alias instead of a dev dependency") 45 | }); 46 | devRemoveNode->addPositionalArgument(QStringLiteral("packages"), 47 | tr("The packages to remove from the dev packages"), 48 | QStringLiteral("::@ ...")); 49 | 50 | auto devCommitNode = devNode->addLeafNode(QStringLiteral("commit"), 51 | tr("Publish a dev package using \"qpmx publish\" and then " 52 | "remove it from beeing a dev package.")); 53 | devCommitNode->addPositionalArgument(QStringLiteral("package"), 54 | tr("The package(s) to publish and remove from the dev packages."), 55 | QStringLiteral("::@")); 56 | devCommitNode->addPositionalArgument(QStringLiteral("version"), 57 | tr("The new version for the preceeding package to publish."), 58 | QStringLiteral(" ...")); 59 | devCommitNode->addOption({ 60 | QStringLiteral("no-add"), 61 | tr("Don't update/add the commited package in the user.json file. By default thats the case " 62 | "for comitting") 63 | }); 64 | devCommitNode->addOption({ 65 | {QStringLiteral("p"), QStringLiteral("provider")}, 66 | tr("Pass the to push to. Can be specified multiple times. If not specified, " 67 | "the package is published for all providers prepared for the package."), 68 | tr("provider") 69 | }); 70 | 71 | auto devAliasNode = devNode->addLeafNode(QStringLiteral("alias"), 72 | tr("Create an alias for a package to replace it in builds.")); 73 | devAliasNode->addPositionalArgument(QStringLiteral("original package"), 74 | tr("The package(s) to be replaced by an alias"), 75 | QStringLiteral("[::@")); 76 | devAliasNode->addPositionalArgument(QStringLiteral("alias package"), 77 | tr("The package(s) to use as the replacement"), 78 | QStringLiteral("::@ ...]")); 79 | 80 | return devNode; 81 | } 82 | 83 | void DevCommand::initialize(QCliParser &parser) 84 | { 85 | try { 86 | if(parser.enterContext(QStringLiteral("add"))) 87 | addDev(parser); 88 | else if(parser.enterContext(QStringLiteral("alias"))) 89 | addAlias(parser); 90 | else if(parser.enterContext(QStringLiteral("remove"))) { 91 | if(parser.isSet(QStringLiteral("alias"))) 92 | removeAlias(parser); 93 | else 94 | removeDev(parser); 95 | } else if(parser.enterContext(QStringLiteral("commit"))) 96 | commitDev(parser); 97 | else 98 | Q_UNREACHABLE(); 99 | 100 | //clean local caches 101 | setDevMode(true); 102 | QDir devCache = buildDir(); 103 | setDevMode(false); 104 | if(devCache.exists() && !devCache.removeRecursively()) { 105 | xWarning() << tr("Failed to remove dev caches! This can corrupt your builds. " 106 | "Please remove the folder yourself: %{bld}%1%{end}") 107 | .arg(devCache.absolutePath()); 108 | } 109 | 110 | qApp->quit(); 111 | } catch (QString &s) { 112 | xCritical() << s; 113 | } 114 | parser.leaveContext(); 115 | } 116 | 117 | void DevCommand::addDev(const QCliParser &parser) 118 | { 119 | if(parser.positionalArguments().isEmpty()) 120 | throw tr("You must specify at least one package and path"); 121 | if((parser.positionalArguments().size() %2) != 0) 122 | throw tr("You must specify pairs of package and a local path"); 123 | 124 | auto userFormat = QpmxUserFormat::readDefault(); 125 | for(auto i = 0; i < parser.positionalArguments().size(); i += 2) { 126 | auto pkgList = readCliPackages(parser.positionalArguments().mid(i, 1), true); 127 | if(pkgList.size() != 1) 128 | throw tr("You must specify a package and a local path"); 129 | auto pkg = pkgList.first(); 130 | 131 | auto path = parser.positionalArguments().value(i + 1); 132 | if(!QFile::exists(path)) 133 | throw tr("The given local path \"%1\" is not valid").arg(path); 134 | 135 | QpmxDevDependency devDep(pkg, path); 136 | auto dIdx = userFormat.devDependencies.indexOf(devDep); 137 | if(dIdx != -1) { 138 | xWarning() << tr("Package %1 is already a dev dependency. Replacing with the given version and path").arg(devDep.toString()); 139 | userFormat.devDependencies[dIdx] = devDep; 140 | } else 141 | userFormat.devDependencies.append(devDep); 142 | 143 | xDebug() << tr("Added package %1 as dev dependency with path \"%2\"") 144 | .arg(devDep.toString(), path); 145 | } 146 | 147 | QpmxUserFormat::writeUser(userFormat); 148 | } 149 | 150 | void DevCommand::removeDev(const QCliParser &parser) 151 | { 152 | auto packages = readCliPackages(parser.positionalArguments(), true); 153 | 154 | auto userFormat = QpmxUserFormat::readDefault(); 155 | for(const auto &package : packages){ 156 | userFormat.devDependencies.removeOne(QpmxDevDependency(package)); 157 | xDebug() << tr("Removed package %1 from the dev dependencies") 158 | .arg(package.toString()); 159 | } 160 | 161 | QpmxUserFormat::writeUser(userFormat); 162 | } 163 | 164 | void DevCommand::commitDev(const QCliParser &parser) 165 | { 166 | if(parser.positionalArguments().isEmpty()) 167 | throw tr("You must specify at least one package and path"); 168 | if((parser.positionalArguments().size() %2) != 0) 169 | throw tr("You must specify pairs of package and version"); 170 | 171 | auto noAdd = parser.isSet(QStringLiteral("no-add")); 172 | 173 | auto userFormat = QpmxUserFormat::readDefault(); 174 | for(auto i = 0; i < parser.positionalArguments().size(); i += 2) { 175 | auto pkgList = readCliPackages(parser.positionalArguments().mid(i, 1), true); 176 | if(pkgList.size() != 1) 177 | throw tr("You must specify a package and a version"); 178 | auto pkg = pkgList.takeFirst(); 179 | 180 | QpmxDevDependency dep(pkg); 181 | auto depIndex = userFormat.devDependencies.indexOf(dep); 182 | if(depIndex == -1) 183 | throw tr("Package %1 is not a dev dependency and cannot be commited").arg(pkg.toString()); 184 | dep = userFormat.devDependencies.value(depIndex); 185 | 186 | auto version = QVersionNumber::fromString(parser.positionalArguments().value(i + 1)); 187 | if(version.isNull()) 188 | throw tr("The given version \"%1\" is not valid").arg(version.toString()); 189 | 190 | //run qpmx publish 191 | runPublish(parser.values(QStringLiteral("provider")), dep, version); 192 | 193 | //remove the dev dep 194 | userFormat.devDependencies.removeOne(dep); 195 | xDebug() << tr("Removed package %1 from the dev dependencies") 196 | .arg(pkg.toString()); 197 | 198 | //add it to qpmx.json 199 | if(!noAdd) { 200 | dep.version = version;//update version and update in normal format 201 | userFormat.putDependency(dep); 202 | } 203 | } 204 | 205 | QpmxUserFormat::writeUser(userFormat); 206 | if(!noAdd) 207 | QpmxFormat::writeDefault(userFormat); 208 | } 209 | 210 | void DevCommand::addAlias(const QCliParser &parser) 211 | { 212 | if(parser.positionalArguments().isEmpty()) 213 | throw tr("You must specify at least one package and path"); 214 | if((parser.positionalArguments().size() %2) != 0) 215 | throw tr("You must specify pairs of original and alias packages"); 216 | 217 | auto userFormat = QpmxUserFormat::readDefault(); 218 | for(auto i = 0; i < parser.positionalArguments().size(); i += 2) { 219 | auto pkgList = readCliPackages(parser.positionalArguments().mid(i, 2), true); 220 | if(pkgList.size() != 2) 221 | throw tr("You must specify an original and an alias package"); 222 | auto original = pkgList.first(); 223 | auto alias = pkgList.last(); 224 | 225 | QpmxDevAlias devAlias(original, alias); 226 | auto dIdx = userFormat.devAliases.indexOf(devAlias); 227 | if(dIdx == -1) 228 | userFormat.devAliases.append(devAlias); 229 | else { 230 | xWarning() << tr("Package %1 already has an alias. Replacing with the given version and path").arg(devAlias.original.toString()); 231 | userFormat.devAliases[dIdx] = devAlias; 232 | } 233 | 234 | xDebug() << tr("Added package %1 as alias for %2") 235 | .arg(devAlias.alias.toString(), devAlias.original.toString()); 236 | } 237 | 238 | QpmxUserFormat::writeUser(userFormat); 239 | } 240 | 241 | void DevCommand::removeAlias(const QCliParser &parser) 242 | { 243 | auto packages = readCliPackages(parser.positionalArguments(), true); 244 | 245 | auto userFormat = QpmxUserFormat::readDefault(); 246 | for(const auto &package : packages){ 247 | userFormat.devAliases.removeOne(QpmxDevAlias(package)); 248 | xDebug() << tr("Removed package %1 from the aliases") 249 | .arg(package.toString()); 250 | } 251 | 252 | QpmxUserFormat::writeUser(userFormat); 253 | } 254 | 255 | void DevCommand::runPublish(const QStringList &providers, const QpmxDevDependency &dep, const QVersionNumber &version) 256 | { 257 | QStringList args = { 258 | QStringLiteral("publish"), 259 | version.toString() 260 | }; 261 | args.reserve(providers.size() + args.size()); 262 | for(const auto &provider : providers) 263 | args.append({QStringLiteral("--provider"), provider}); 264 | 265 | xInfo() << tr("\nPublishing package %1").arg(dep.toString()); 266 | subCall(args, QDir::current().absoluteFilePath(dep.path)); 267 | xDebug() << tr("Successfully published package %1").arg(dep.toString()); 268 | } 269 | 270 | -------------------------------------------------------------------------------- /qpmx/qpmxformat.cpp: -------------------------------------------------------------------------------- 1 | #include "qpmxformat.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace qpmx; 7 | 8 | QpmxDependency::QpmxDependency() = default; 9 | 10 | QpmxDependency::QpmxDependency(const PackageInfo &package) : 11 | provider(package.provider()), 12 | package(package.package()), 13 | version(package.version()) 14 | {} 15 | 16 | QpmxDependency::~QpmxDependency() = default; 17 | 18 | bool QpmxDependency::operator==(const QpmxDependency &other) const 19 | { 20 | //only provider and package "identify" the dependency 21 | return provider == other.provider && 22 | package == other.package; 23 | } 24 | 25 | bool QpmxDependency::operator!=(const QpmxDependency &other) const 26 | { 27 | //only provider and package "identify" the dependency 28 | return provider != other.provider || 29 | package != other.package; 30 | } 31 | 32 | QString QpmxDependency::toString(bool scoped) const 33 | { 34 | return pkg().toString(scoped); 35 | } 36 | 37 | bool QpmxDependency::isComplete() const 38 | { 39 | return !provider.isEmpty() && 40 | !package.isEmpty() && 41 | !version.isNull(); 42 | } 43 | 44 | PackageInfo QpmxDependency::pkg(const QString &provider) const 45 | { 46 | return {provider.isEmpty() ? this->provider : provider, package, version}; 47 | } 48 | 49 | 50 | 51 | bool QpmxFormatLicense::operator!=(const QpmxFormatLicense &other) const 52 | { 53 | return name != other.name || 54 | file != other.file; 55 | } 56 | 57 | 58 | 59 | QpmxFormat::~QpmxFormat() = default; 60 | 61 | QpmxFormat QpmxFormat::readFile(const QDir &dir, bool mustExist) 62 | { 63 | return readFile(dir, QStringLiteral("./qpmx.json"), mustExist); 64 | } 65 | 66 | QpmxFormat QpmxFormat::readFile(const QDir &dir, const QString &fileName, bool mustExist) 67 | { 68 | QFile qpmxFile(dir.absoluteFilePath(fileName)); 69 | if(qpmxFile.exists()) { 70 | if(!qpmxFile.open(QIODevice::ReadOnly | QIODevice::Text)) { 71 | throw tr("Failed to open %1 with error: %2") 72 | .arg(qpmxFile.fileName(), qpmxFile.errorString()); 73 | } 74 | 75 | try { 76 | QJsonSerializer ser; 77 | auto format = ser.deserializeFrom(&qpmxFile); 78 | format.checkDuplicates(); 79 | return format; 80 | } catch(QJsonSerializerException &e) { 81 | qDebug() << e.what(); 82 | throw tr("%1 contains invalid data").arg(qpmxFile.fileName()); 83 | } 84 | } else if(mustExist) 85 | throw tr("%1 does not exist").arg(qpmxFile.fileName()); 86 | else 87 | return {}; 88 | } 89 | 90 | QpmxFormat QpmxFormat::readDefault(bool mustExist) 91 | { 92 | return readFile(QDir::current(), mustExist); 93 | } 94 | 95 | void QpmxFormat::writeDefault(const QpmxFormat &data) 96 | { 97 | QSaveFile qpmxFile(QStringLiteral("./qpmx.json")); 98 | if(!qpmxFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 99 | throw tr("Failed to open %1 with error: %2") 100 | .arg(qpmxFile.fileName(), qpmxFile.errorString()); 101 | } 102 | 103 | try { 104 | QJsonSerializer ser; 105 | ser.serializeTo(&qpmxFile, data, QJsonDocument::Indented); 106 | } catch(QJsonSerializerException &e) { 107 | qDebug() << e.what(); 108 | throw tr("Failed to write %1").arg(qpmxFile.fileName()); 109 | } 110 | 111 | if(!qpmxFile.commit()) { 112 | throw tr("Failed to save %1 with error: %2") 113 | .arg(qpmxFile.fileName(), qpmxFile.errorString()); 114 | } 115 | } 116 | 117 | void QpmxFormat::putDependency(const QpmxDependency &dep) 118 | { 119 | auto depIndex = dependencies.indexOf(dep); 120 | if(depIndex == -1) { 121 | qDebug().noquote() << tr("Added package %1 to qpmx.json").arg(dep.toString()); 122 | dependencies.append(dep); 123 | } else { 124 | qWarning().noquote() << tr("Package %1 is already a dependency. Replacing with that version") 125 | .arg(dep.toString()); 126 | dependencies[depIndex] = dep; 127 | } 128 | } 129 | 130 | void QpmxFormat::checkDuplicates() 131 | { 132 | checkDuplicatesImpl(dependencies); 133 | } 134 | 135 | template 136 | void QpmxFormat::checkDuplicatesImpl(const QList &data) 137 | { 138 | static_assert(std::is_base_of::value, "checkDuplicates is only available for QpmxDependency classes"); 139 | for(auto i = 0; i < data.size(); i++) { 140 | if(data[i].provider.isEmpty()) 141 | throw tr("Dependency does not have a provider: %1").arg(data[i].toString()); 142 | if(data[i].package.isEmpty()) 143 | throw tr("Dependency does not have a package: %1").arg(data[i].toString()); 144 | if(data[i].version.isNull()) 145 | throw tr("Dependency does not have a version: %1").arg(data[i].toString()); 146 | 147 | if(i < data.size() - 1) { 148 | if(data.indexOf(data[i], i + 1) != -1) 149 | throw tr("Duplicated dependency found: %1").arg(data[i].toString()); 150 | } 151 | } 152 | } 153 | 154 | 155 | 156 | QpmxDevDependency::QpmxDevDependency() = default; 157 | 158 | QpmxDevDependency::QpmxDevDependency(const QpmxDependency &dep, QString localPath) : 159 | QpmxDependency(dep), 160 | path(std::move(localPath)) 161 | {} 162 | 163 | bool QpmxDevDependency::isDev() const 164 | { 165 | return !path.isNull(); 166 | } 167 | 168 | bool QpmxDevDependency::operator==(const QpmxDependency &other) const 169 | { 170 | return QpmxDependency::operator ==(other); 171 | } 172 | 173 | 174 | 175 | QpmxDevAlias::QpmxDevAlias() = default; 176 | 177 | QpmxDevAlias::QpmxDevAlias(const QpmxDependency &original, const QpmxDependency &alias) : 178 | original(original), 179 | alias(alias) 180 | {} 181 | 182 | bool QpmxDevAlias::operator==(const QpmxDependency &orig) const 183 | { 184 | return original == orig && 185 | original.version == orig.version; 186 | } 187 | 188 | bool QpmxDevAlias::operator==(const QpmxDevAlias &other) const 189 | { 190 | return operator==(other.original); 191 | } 192 | 193 | 194 | 195 | QpmxUserFormat::QpmxUserFormat() = default; 196 | 197 | QpmxUserFormat::QpmxUserFormat(const QpmxUserFormat &userFormat, const QpmxFormat &format) : 198 | QpmxFormat(format), 199 | devDependencies(userFormat.devDependencies), 200 | devAliases(userFormat.devAliases) 201 | { 202 | // replace all aliases 203 | if(!devAliases.isEmpty()) { 204 | for(auto &dep : dependencies) { 205 | auto index = devAliases.indexOf(dep); 206 | if(index != -1) 207 | dep = devAliases[index].alias; 208 | } 209 | } 210 | // remove all dev dep duplicates 211 | for(const auto &dep : qAsConst(devDependencies)) 212 | dependencies.removeOne(dep); 213 | } 214 | 215 | QList QpmxUserFormat::allDeps() const 216 | { 217 | auto res = devDependencies; 218 | res.reserve(res.size() + dependencies.size()); 219 | for(const auto &dep : qAsConst(dependencies)) 220 | res.append(dep); 221 | return res; 222 | } 223 | 224 | bool QpmxUserFormat::hasDevOptions() const 225 | { 226 | return !devDependencies.isEmpty() || !devAliases.isEmpty(); 227 | } 228 | 229 | QpmxUserFormat QpmxUserFormat::readDefault(bool mustExist) 230 | { 231 | auto baseFormat = QpmxFormat::readDefault(mustExist); 232 | auto userFormat = readFile(QDir::current(), QStringLiteral("qpmx.json.user"), false); 233 | return {userFormat, baseFormat}; 234 | } 235 | 236 | QpmxUserFormat QpmxUserFormat::readFile(const QDir &dir, const QString &fileName, bool mustExist) 237 | { 238 | QFile qpmxUserFile(dir.absoluteFilePath(fileName)); 239 | if(qpmxUserFile.exists()) { 240 | if(!qpmxUserFile.open(QIODevice::ReadOnly | QIODevice::Text)) { 241 | throw tr("Failed to open %1 with error: %2") 242 | .arg(qpmxUserFile.fileName(), qpmxUserFile.errorString()); 243 | } 244 | 245 | try { 246 | QJsonSerializer ser; 247 | auto format = ser.deserializeFrom(&qpmxUserFile); 248 | format.checkDuplicates(); 249 | return format; 250 | } catch(QJsonSerializerException &e) { 251 | qDebug() << e.what(); 252 | throw tr("%1 contains invalid data").arg(qpmxUserFile.fileName()); 253 | } 254 | } else if(mustExist) 255 | throw tr("%1 does not exist").arg(qpmxUserFile.fileName()); 256 | else 257 | return {}; 258 | } 259 | 260 | void QpmxUserFormat::writeUser(const QpmxUserFormat &data) 261 | { 262 | QSaveFile qpmxUserFile(QDir::current().absoluteFilePath(QStringLiteral("qpmx.json.user"))); 263 | if(!qpmxUserFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 264 | throw tr("Failed to open %1 with error: %2") 265 | .arg(qpmxUserFile.fileName(), qpmxUserFile.errorString()); 266 | } 267 | 268 | try { 269 | QJsonSerializer ser; 270 | auto json = ser.serialize(data); 271 | 272 | QJsonObject userReduced; 273 | for(auto i = staticMetaObject.propertyOffset(); i < staticMetaObject.propertyCount(); i++) { 274 | auto prop = staticMetaObject.property(i); 275 | auto name = QString::fromUtf8(prop.name()); 276 | userReduced[name] = json[name]; 277 | } 278 | 279 | qpmxUserFile.write(QJsonDocument(userReduced).toJson(QJsonDocument::Indented)); 280 | } catch(QJsonSerializerException &e) { 281 | qDebug() << e.what(); 282 | throw tr("Failed to write %1").arg(qpmxUserFile.fileName()); 283 | } 284 | 285 | if(!qpmxUserFile.commit()) { 286 | throw tr("Failed to save %1 with error: %2") 287 | .arg(qpmxUserFile.fileName(), qpmxUserFile.errorString()); 288 | } 289 | } 290 | 291 | void QpmxUserFormat::checkDuplicates() 292 | { 293 | QpmxFormat::checkDuplicates(); 294 | checkDuplicatesImpl(devDependencies); 295 | } 296 | 297 | void QpmxUserFormat::setDevmodeSafe(const QList &data) 298 | { 299 | if(data.isEmpty()) 300 | return; 301 | else if(!devDependencies.isEmpty()) 302 | qWarning().noquote() << tr("Both devDependencies and devmode are set! Ignoring devmode"); 303 | else 304 | devDependencies = data; 305 | } 306 | 307 | QList QpmxUserFormat::readDummy() const 308 | { 309 | return {}; 310 | } 311 | 312 | 313 | 314 | QpmxCacheFormat::QpmxCacheFormat() = default; 315 | 316 | QpmxCacheFormat::QpmxCacheFormat(const QpmxUserFormat &userFormat, QString kitId) : 317 | QpmxUserFormat(userFormat), 318 | buildKit(std::move(kitId)) 319 | {} 320 | 321 | QpmxCacheFormat QpmxCacheFormat::readCached(const QDir &dir) 322 | { 323 | QFile qpmxCacheFile(dir.absoluteFilePath(QStringLiteral(".qpmx.cache"))); 324 | if(qpmxCacheFile.exists()) { 325 | if(!qpmxCacheFile.open(QIODevice::ReadOnly | QIODevice::Text)) { 326 | throw tr("Failed to open %1 with error: %2") 327 | .arg(qpmxCacheFile.fileName(), qpmxCacheFile.errorString()); 328 | } 329 | 330 | try { 331 | QJsonSerializer ser; 332 | auto format = ser.deserializeFrom(&qpmxCacheFile); 333 | format.checkDuplicates(); 334 | return format; 335 | } catch(QJsonSerializerException &e) { 336 | qDebug() << e.what(); 337 | throw tr("%1 contains invalid data").arg(qpmxCacheFile.fileName()); 338 | } 339 | } else 340 | return {}; 341 | } 342 | 343 | bool QpmxCacheFormat::writeCached(const QDir &dir, const QpmxCacheFormat &data) 344 | { 345 | QSaveFile qpmxCacheFile(dir.absoluteFilePath(QStringLiteral(".qpmx.cache"))); 346 | if(!qpmxCacheFile.open(QIODevice::WriteOnly | QIODevice::Text)) { 347 | qWarning().noquote() << tr("Failed to open %1 with error: %2") 348 | .arg(qpmxCacheFile.fileName(), qpmxCacheFile.errorString()); 349 | return false; 350 | } 351 | 352 | try { 353 | QJsonSerializer ser; 354 | ser.serializeTo(&qpmxCacheFile, data, QJsonDocument::Compact); 355 | } catch(QJsonSerializerException &e) { 356 | qDebug() << e.what(); 357 | qWarning().noquote() << tr("Failed to write %1").arg(qpmxCacheFile.fileName()); 358 | return false; 359 | } 360 | 361 | if(!qpmxCacheFile.commit()) { 362 | qWarning().noquote() << tr("Failed to save %1 with error: %2") 363 | .arg(qpmxCacheFile.fileName(), qpmxCacheFile.errorString()); 364 | return false; 365 | } else 366 | return true; 367 | } 368 | -------------------------------------------------------------------------------- /plugins/gitsource/gitsourceplugin.cpp: -------------------------------------------------------------------------------- 1 | #include "gitsourceplugin.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define print(x) do { \ 16 | std::cout << QString(x).toStdString(); \ 17 | std::cout.flush(); \ 18 | } while(false) 19 | 20 | QRegularExpression GitSourcePlugin::_githubRegex(QStringLiteral(R"__(^com\.github\.([^\.#]*)\.([^\.#]*)(?:#(.*))?$)__")); 21 | 22 | GitSourcePlugin::GitSourcePlugin(QObject *parent) : 23 | QObject{parent} 24 | {} 25 | 26 | bool GitSourcePlugin::canSearch(const QString &provider) const 27 | { 28 | Q_UNUSED(provider) 29 | return false; 30 | } 31 | 32 | bool GitSourcePlugin::canPublish(const QString &provider) const 33 | { 34 | Q_UNUSED(provider) 35 | return true; 36 | } 37 | 38 | QString GitSourcePlugin::packageSyntax(const QString &provider) const 39 | { 40 | if(provider == QStringLiteral("git")) 41 | return tr("[#]"); 42 | else if(provider == QStringLiteral("github")) 43 | return tr("com.github..[#]"); 44 | else 45 | return {}; 46 | } 47 | 48 | bool GitSourcePlugin::packageValid(const qpmx::PackageInfo &package) const 49 | { 50 | if(package.provider() == QStringLiteral("git")) { 51 | QUrl url(package.package()); 52 | return url.isValid() && url.path().endsWith(QStringLiteral(".git")); 53 | } else if(package.provider() == QStringLiteral("github")) 54 | return _githubRegex.match(package.package()).hasMatch(); 55 | else 56 | return false; 57 | } 58 | 59 | QJsonObject GitSourcePlugin::createPublisherInfo(const QString &provider) 60 | { 61 | QFile console; 62 | if(!console.open(stdin, QIODevice::ReadOnly | QIODevice::Text)) 63 | throw qpmx::SourcePluginException{tr("Failed to access console with error: %1").arg(console.errorString())}; 64 | 65 | QTextStream stream(&console); 66 | if(provider == QStringLiteral("git")) { 67 | QString pUrl; 68 | forever { 69 | print(tr("Enter the remote-url to push the package to: ")); 70 | pUrl = stream.readLine().trimmed().toLower(); 71 | QUrl url(pUrl); 72 | if(!url.isValid() || !url.path().endsWith(QStringLiteral(".git"))) 73 | qWarning().noquote() << tr("Invalid url! Enter a valid url that ends with \".git\""); 74 | else 75 | break; 76 | } 77 | 78 | print(tr("Enter a version prefix (optional): ")); 79 | auto pPrefix = stream.readLine().trimmed().toLower(); 80 | 81 | QJsonObject object; 82 | object[QStringLiteral("url")] = pUrl; 83 | object[QStringLiteral("prefix")] = pPrefix; 84 | return object; 85 | } else if(provider == QStringLiteral("github")) { 86 | QJsonObject object; 87 | print(tr("Enter the users name to push to: ")); 88 | object[QStringLiteral("user")] = stream.readLine().trimmed().toLower(); 89 | print(tr("Enter the repository name (NOT url) to push to: ")); 90 | object[QStringLiteral("repository")] = stream.readLine().trimmed().toLower(); 91 | print(tr("Enter a version prefix (optional): ")); 92 | object[QStringLiteral("prefix")] = stream.readLine().trimmed().toLower(); 93 | return object; 94 | } else 95 | return {}; 96 | } 97 | 98 | QStringList GitSourcePlugin::searchPackage(const QString &provider, const QString &query) 99 | { 100 | Q_UNUSED(query); 101 | Q_UNUSED(provider); 102 | return {}; 103 | } 104 | 105 | QVersionNumber GitSourcePlugin::findPackageVersion(const qpmx::PackageInfo &package) 106 | { 107 | QString prefix; 108 | auto url = pkgUrl(package, &prefix); 109 | 110 | QStringList arguments { 111 | QStringLiteral("ls-remote"), 112 | QStringLiteral("--tags"), 113 | QStringLiteral("--exit-code"), 114 | url 115 | }; 116 | if(!package.version().isNull()) 117 | arguments.append(pkgTag(package)); 118 | else if(!prefix.isNull()) 119 | arguments.append(prefix + QLatin1Char('*')); 120 | 121 | auto proc = createProcess(arguments, true); 122 | _processCache.insert(proc); 123 | qDebug().noquote() << tr("Listing remote for repository %1").arg(url); 124 | auto res = QtCoroutine::await(proc); 125 | _processCache.remove(proc); 126 | proc->deleteLater(); 127 | 128 | if(res == EXIT_SUCCESS) { 129 | //parse output 130 | qDebug().noquote() << tr("Parsing ls-remote output"); 131 | QSet versions; 132 | QRegularExpression tagRegex(QStringLiteral(R"__(^\w+\trefs\/tags\/(.*)$)__")); 133 | for(const auto &line : proc->readAllStandardOutput().split('\n')) { 134 | auto match = tagRegex.match(QString::fromUtf8(line)); 135 | if(match.hasMatch()) 136 | versions.insert(QVersionNumber::fromString(match.captured(1))); 137 | } 138 | 139 | auto vList = versions.toList(); 140 | std::sort(vList.begin(), vList.end()); 141 | if(vList.isEmpty()) 142 | return {}; 143 | else 144 | return vList.last(); 145 | } else if(res == 2) 146 | return {}; 147 | else 148 | throw qpmx::SourcePluginException{formatProcError(tr("list versions"), proc)}; 149 | } 150 | 151 | void GitSourcePlugin::getPackageSource(const qpmx::PackageInfo &package, const QDir &targetDir) 152 | { 153 | auto url = pkgUrl(package); 154 | auto tag = pkgTag(package); 155 | 156 | QStringList arguments { 157 | QStringLiteral("clone"), 158 | QStringLiteral("--recurse-submodules"), 159 | QStringLiteral("-j%1").arg(QThread::idealThreadCount()), 160 | QStringLiteral("--depth"), 161 | QStringLiteral("1"), 162 | QStringLiteral("--branch"), 163 | tag, 164 | url, 165 | targetDir.absolutePath() 166 | }; 167 | 168 | auto proc = createProcess(arguments); 169 | _processCache.insert(proc); 170 | qDebug().noquote() << tr("Cloning git repository %1").arg(url); 171 | auto res = QtCoroutine::await(proc); 172 | _processCache.remove(proc); 173 | proc->deleteLater(); 174 | if(res == EXIT_SUCCESS) { 175 | auto tDir = targetDir; 176 | if(tDir.cd(QStringLiteral(".git"))) 177 | tDir.removeRecursively(); 178 | } else 179 | throw qpmx::SourcePluginException{formatProcError(tr("clone versions"), proc)}; 180 | } 181 | 182 | void GitSourcePlugin::publishPackage(const QString &provider, const QDir &qpmxDir, const QVersionNumber &version, const QJsonObject &publisherInfo) 183 | { 184 | QString url; 185 | if(provider == QStringLiteral("git")) 186 | url = publisherInfo[QStringLiteral("url")].toString(); 187 | else if(provider == QStringLiteral("github")) { 188 | url = QStringLiteral("https://github.com/%1/%2.git") 189 | .arg(publisherInfo[QStringLiteral("user")].toString(), publisherInfo[QStringLiteral("repository")].toString()); 190 | } else 191 | throw qpmx::SourcePluginException{tr("Unsupported provider")}; 192 | 193 | auto tag = version.toString(); 194 | auto prefix = publisherInfo[QStringLiteral("prefix")].toString(); 195 | if(!prefix.isEmpty()) 196 | tag.prepend(prefix); 197 | 198 | //verify the url and get the origin 199 | QString remote; 200 | QSettings gitConfig{qpmxDir.absoluteFilePath(QStringLiteral(".git/config")), QSettings::IniFormat}; 201 | QRegularExpression regex(QStringLiteral(R"__(remote\s*"(.+)")__")); 202 | for(const auto &group : gitConfig.childGroups()) { 203 | auto match = regex.match(group); 204 | if(match.hasMatch()) { 205 | gitConfig.beginGroup(group); 206 | if(gitConfig.value(QStringLiteral("url")).toString() == url) { 207 | gitConfig.endGroup(); 208 | remote = match.captured(1); 209 | break; 210 | } 211 | gitConfig.endGroup(); 212 | } 213 | } 214 | if(remote.isEmpty()) { 215 | qWarning().noquote() << tr("Unable to determine remote based of url. Pushing to url directly"); 216 | remote = url; 217 | } 218 | 219 | //create the tag 220 | QStringList arguments { 221 | QStringLiteral("tag"), 222 | tag 223 | }; 224 | auto proc = createProcess(arguments); 225 | _processCache.insert(proc); 226 | qDebug().noquote() << tr("Create new tag %{bld}%1%{end}").arg(tag); 227 | auto res = QtCoroutine::await(proc); 228 | _processCache.remove(proc); 229 | proc->deleteLater(); 230 | if(res != EXIT_SUCCESS) 231 | throw qpmx::SourcePluginException{formatProcError(tr("create tag"), proc)}; 232 | 233 | //push the tag to origin 234 | arguments = QStringList { 235 | QStringLiteral("push"), 236 | remote, 237 | tag 238 | }; 239 | proc = createProcess(arguments, true); 240 | proc->setProcessChannelMode(QProcess::ForwardedOutputChannel); 241 | proc->setInputChannelMode(QProcess::ForwardedInputChannel); 242 | _processCache.insert(proc); 243 | qDebug().noquote() << tr("Pushing tag to remote \"%1\"").arg(remote); 244 | res = QtCoroutine::await(proc); 245 | _processCache.remove(proc); 246 | proc->deleteLater(); 247 | if(res != EXIT_SUCCESS) 248 | throw qpmx::SourcePluginException{formatProcError(tr("push tag to remote"), proc)}; 249 | } 250 | 251 | void GitSourcePlugin::cancelAll(int timeout) 252 | { 253 | auto procs = _processCache; 254 | _processCache.clear(); 255 | 256 | for(auto proc : procs) { 257 | proc->disconnect(); 258 | proc->terminate(); 259 | } 260 | 261 | for(auto proc : procs) { 262 | auto startTime = QDateTime::currentMSecsSinceEpoch(); 263 | if(!proc->waitForFinished(timeout)) { 264 | timeout = 1; 265 | proc->kill(); 266 | proc->waitForFinished(100); 267 | } else { 268 | auto endTime = QDateTime::currentMSecsSinceEpoch(); 269 | timeout = static_cast(qMax(1ll, timeout - (endTime - startTime))); 270 | } 271 | } 272 | } 273 | 274 | 275 | QString GitSourcePlugin::pkgUrl(const qpmx::PackageInfo &package, QString *prefix) 276 | { 277 | QString pkgUrl; 278 | if(package.provider() == QStringLiteral("git")) 279 | pkgUrl = package.package(); 280 | else if(package.provider() == QStringLiteral("github")) { 281 | auto match = _githubRegex.match(package.package()); 282 | if(!match.hasMatch()) 283 | throw qpmx::SourcePluginException{tr("The Package %1 is not a valid github package").arg(package.toString())}; 284 | pkgUrl = QStringLiteral("https://github.com/%1/%2.git") 285 | .arg(match.captured(1), match.captured(2)); 286 | if(!match.captured(3).isEmpty()) 287 | pkgUrl += QLatin1Char('#') + match.captured(3); 288 | } else 289 | throw qpmx::SourcePluginException{tr("Unsupported provider %{bld}%1%{end}").arg(package.provider())}; 290 | 291 | if(prefix) { 292 | QUrl pUrl(pkgUrl); 293 | if(pUrl.hasFragment()) 294 | *prefix = pUrl.fragment(); 295 | else 296 | prefix->clear(); 297 | } 298 | return pkgUrl; 299 | } 300 | 301 | QString GitSourcePlugin::pkgTag(const qpmx::PackageInfo &package) 302 | { 303 | QString prefix; 304 | QUrl packageUrl = pkgUrl(package, &prefix); 305 | if(!packageUrl.isValid()) 306 | throw qpmx::SourcePluginException{tr("The name of package %1 is not a valid url").arg(package.toString())}; 307 | 308 | QString tag; 309 | if(!prefix.isNull()) { 310 | if(prefix.contains(QStringLiteral("%1"))) 311 | tag = prefix.arg(package.version().toString()); 312 | else 313 | tag = prefix + package.version().toString(); 314 | } else 315 | tag = package.version().toString(); 316 | return tag; 317 | } 318 | 319 | QProcess *GitSourcePlugin::createProcess(const QStringList &arguments, bool keepStdout) 320 | { 321 | auto proc = new QProcess(this); 322 | proc->setProgram(QStandardPaths::findExecutable(QStringLiteral("git"))); 323 | if(!keepStdout) 324 | proc->setStandardOutputFile(QProcess::nullDevice()); 325 | proc->setArguments(arguments); 326 | return proc; 327 | } 328 | 329 | QString GitSourcePlugin::formatProcError(const QString &type, QProcess *proc) 330 | { 331 | auto res = tr("Failed to %1 with exit code %2 and stderr:") 332 | .arg(type) 333 | .arg(proc->exitCode()); 334 | proc->setReadChannel(QProcess::StandardError); 335 | while(!proc->atEnd()) { 336 | auto line = QString::fromUtf8(proc->readLine()).trimmed(); 337 | res.append(QStringLiteral("\n\t%1").arg(line)); 338 | } 339 | return res; 340 | } 341 | -------------------------------------------------------------------------------- /qpmx/installcommand.cpp: -------------------------------------------------------------------------------- 1 | #include "initcommand.h" 2 | #include "installcommand.h" 3 | #include 4 | #include 5 | #include 6 | using namespace qpmx; 7 | 8 | InstallCommand::InstallCommand(QObject *parent) : 9 | Command{parent} 10 | {} 11 | 12 | QString InstallCommand::commandName() const 13 | { 14 | return QStringLiteral("install"); 15 | } 16 | 17 | QString InstallCommand::commandDescription() const 18 | { 19 | return tr("Install a qpmx package for the current project."); 20 | } 21 | 22 | QSharedPointer InstallCommand::createCliNode() const 23 | { 24 | auto installNode = QSharedPointer::create(); 25 | installNode->addOption({ 26 | {QStringLiteral("r"), QStringLiteral("renew")}, 27 | tr("Force a reinstallation. If the package was already downloaded, " 28 | "the existing sources and other artifacts will be deleted before downloading."), 29 | }); 30 | installNode->addOption({ 31 | {QStringLiteral("c"), QStringLiteral("cache")}, 32 | tr("Only download and cache the sources. Do not add the package to a qpmx.json."), 33 | }); 34 | installNode->addOption({ 35 | QStringLiteral("no-prepare"), 36 | tr("Do not prepare pro-files if the qpmx.json file is newly created."), 37 | }); 38 | installNode->addPositionalArgument(QStringLiteral("packages"), 39 | tr("The packages to be installed. The provider determines which backend to use for the download. " 40 | "If left empty, all providers are searched for the package. If the version is left out, " 41 | "the latest version is installed. If no packages are specified, the packages from the qpmx.json " 42 | "file will be installed."), 43 | QStringLiteral("[[::][@] ...]")); 44 | return installNode; 45 | } 46 | 47 | void InstallCommand::initialize(QCliParser &parser) 48 | { 49 | try { 50 | _renew = parser.isSet(QStringLiteral("renew")); 51 | _noPrepare = parser.isSet(QStringLiteral("no-prepare")); 52 | auto cacheOnly = parser.isSet(QStringLiteral("cache")); 53 | 54 | if(!parser.positionalArguments().isEmpty()) { 55 | xDebug() << tr("Installing %n package(s) from the command line", "", parser.positionalArguments().size()); 56 | _pkgList = devDepList(readCliPackages(parser.positionalArguments())); 57 | if(!cacheOnly) 58 | _addPkgCount = _pkgList.size(); 59 | } else { 60 | auto format = QpmxUserFormat::readDefault(true); 61 | _pkgList = format.allDeps(); 62 | _aliases = format.devAliases; 63 | if(_pkgList.isEmpty()) { 64 | xWarning() << tr("No dependencies found in qpmx.json. Nothing will be done"); 65 | qApp->quit(); 66 | return; 67 | } 68 | if(format.hasDevOptions()) 69 | setDevMode(true); 70 | 71 | xDebug() << tr("Installing %n package(s) from qpmx.json file", "", _pkgList.size()); 72 | } 73 | 74 | getPackages(); 75 | } catch(QString &s) { 76 | xCritical() << s; 77 | } 78 | } 79 | 80 | void InstallCommand::getPackages() 81 | { 82 | for(auto i = 0; i < _pkgList.size(); ++i) { 83 | auto ¤tDep = _pkgList[i]; 84 | if(currentDep.isDev() && !currentDep.isComplete()) 85 | throw tr("dev dependencies cannot be used without a provider/version"); 86 | 87 | // first: find the correct version if nothing but the name was specified 88 | // this will either yield a single package, set to currentDep, or fail in an exception 89 | if(currentDep.version.isNull() && currentDep.provider.isEmpty()) { 90 | const auto allProvs = registry()->providerNames(); 91 | QList foundDeps; 92 | foundDeps.reserve(allProvs.size()); 93 | QtCoroutine::awaitEach(allProvs, [this, currentDep, &foundDeps](const QString &prov) { 94 | auto plugin = registry()->sourcePlugin(prov); 95 | if(plugin->packageValid(currentDep.pkg(prov))) { 96 | auto cpDep = currentDep; 97 | cpDep.provider = prov; 98 | if(getVersion(cpDep, plugin, false)) 99 | foundDeps.append(cpDep); 100 | } 101 | }); 102 | verifyDeps(foundDeps, currentDep); 103 | currentDep = foundDeps.takeFirst(); 104 | } 105 | 106 | // second: provider is not set (but version is) 107 | if(currentDep.provider.isEmpty()) { 108 | Q_ASSERT(!currentDep.version.isNull()); 109 | const auto allProvs = registry()->providerNames(); 110 | QList foundDeps; 111 | foundDeps.reserve(allProvs.size()); 112 | QtCoroutine::awaitEach(allProvs, [this, currentDep, &foundDeps](const QString &prov) { 113 | auto plugin = registry()->sourcePlugin(prov); 114 | if(plugin->packageValid(currentDep.pkg(prov))) { 115 | auto cpDep = currentDep; 116 | cpDep.provider = prov; 117 | if(installPackage(cpDep, plugin, false)) 118 | foundDeps.append(cpDep); 119 | } 120 | }); 121 | verifyDeps(foundDeps, currentDep); 122 | currentDep = foundDeps.takeFirst(); 123 | } else { // third: provider provider set, version may or may not be set 124 | Q_ASSERT(!currentDep.provider.isEmpty()); 125 | auto plugin = registry()->sourcePlugin(currentDep.provider); 126 | if(!plugin->packageValid(currentDep.pkg())) { 127 | throw tr("The package name %1 is not valid for provider %{bld}%2%{end}") 128 | .arg(currentDep.package, currentDep.provider); 129 | } 130 | installPackage(currentDep, plugin, true); 131 | } 132 | } 133 | 134 | if(_addPkgCount > 0) 135 | completeInstall(); 136 | else 137 | xDebug() << tr("Skipping add to qpmx.json, only cache installs"); 138 | xDebug() << tr("Package installation completed"); 139 | qApp->quit(); 140 | return; 141 | } 142 | 143 | void InstallCommand::completeInstall() 144 | { 145 | auto prepare = false; 146 | if(!_noPrepare) { 147 | try { 148 | QpmxFormat::readDefault(true); 149 | } catch(QString &) { 150 | //file does not exist -> prepare if possible 151 | prepare = true; 152 | } 153 | } 154 | 155 | auto format = QpmxFormat::readDefault(); 156 | for(const auto &pkg : _pkgList.mid(0, _addPkgCount)) 157 | format.putDependency(pkg); 158 | QpmxFormat::writeDefault(format); 159 | xInfo() << "Added all packages to qpmx.json"; 160 | 161 | if(prepare) { 162 | auto dir = QDir::current(); 163 | dir.setFilter(QDir::Files); 164 | dir.setNameFilters({QStringLiteral("*.pro")}); 165 | for(const auto &proFile : dir.entryList()) 166 | InitCommand::prepare(proFile, true); 167 | } 168 | } 169 | 170 | bool InstallCommand::getVersion(QpmxDevDependency ¤t, SourcePlugin *plugin, bool mustWork) 171 | { 172 | try { 173 | xDebug() << tr("Searching for latest version of %1").arg(current.toString()); 174 | auto version = plugin->findPackageVersion(current.pkg()); 175 | if(version.isNull()) { 176 | auto str = tr("Package%1does not exist for the given provider"); 177 | if(mustWork) 178 | xCritical() << str.arg(tr(" %1 ").arg(current.toString())); 179 | else 180 | xDebug() << str.arg(tr(" ")); 181 | return false; 182 | } else { 183 | xDebug() << tr("Fetched latest version as %1").arg(version.toString()); 184 | current.version = version; 185 | return true; 186 | } 187 | } catch(SourcePluginException &e) { 188 | const auto str = tr("Failed to get version for package%1with error:"); 189 | if(mustWork) 190 | xCritical() << str.arg(tr(" %1 ").arg(current.toString())) << e.what(); 191 | else 192 | xWarning() << str.arg(tr(" ")) << e.what(); 193 | return false; 194 | } 195 | } 196 | 197 | bool InstallCommand::installPackage(QpmxDevDependency ¤t, SourcePlugin *plugin, bool mustWork) 198 | { 199 | CacheLock lock; // lock is acquired by getSource method 200 | if(!getSource(current, plugin, mustWork, lock)) 201 | return false; 202 | 203 | Q_ASSERT(lock.isLocked()); 204 | auto format = QpmxFormat::readFile(srcDir(current), true); 205 | //create the src_include in the build dir 206 | createSrcInclude(current, format, lock); 207 | //add new dependencies 208 | detectDeps(format); 209 | return true; 210 | } 211 | 212 | bool InstallCommand::getSource(QpmxDevDependency ¤t, SourcePlugin *plugin, bool mustWork, CacheLock &lock) 213 | { 214 | //no version -> fetch first 215 | if(current.version.isNull()) { 216 | //unlock again, then check the version 217 | if(!getVersion(current, plugin, mustWork)) 218 | return false; 219 | // if version was found, lock again and continue the install 220 | } 221 | 222 | //acquire the lock for the package 223 | lock = pkgLock(current); 224 | 225 | //dev dep -> skip to completed 226 | if(current.isDev()) { 227 | xInfo() << tr("Skipping download of dev dependency %1").arg(current.toString()); 228 | return true; 229 | } 230 | 231 | auto sDir = srcDir(current); 232 | if(sDir.exists()) { 233 | if(_renew || !sDir.exists(QStringLiteral("qpmx.json"))) //no qpmx.json -> remove and download again 234 | cleanCaches(current.pkg(), lock); 235 | else { 236 | xDebug() << tr("Sources for package %1 already exist. Skipping download").arg(current.toString()); 237 | return true; 238 | } 239 | } 240 | 241 | try { 242 | QTemporaryDir tDir {tmpDir().absoluteFilePath(QStringLiteral("src.XXXXXX"))}; 243 | if(!tDir.isValid()) 244 | throw tr("Failed to create temporary directory with error: %1").arg(tDir.errorString()); 245 | xDebug() << tr("Gettings sources for package %1").arg(current.toString()); 246 | plugin->getPackageSource(current.pkg(), tDir.path()); 247 | 248 | //load the format from the temp dir 249 | auto format = QpmxFormat::readFile(tDir.path(), true); 250 | //move the sources to cache 251 | tDir.setAutoRemove(false); 252 | QFileInfo path = tDir.path(); 253 | auto oDir = srcDir(current.provider, current.package, {}, true); 254 | auto vSubDir = oDir.absoluteFilePath(current.version.toString()); 255 | if(!path.dir().rename(path.fileName(), vSubDir)) 256 | throw tr("Failed to move downloaded sources of %1 from temporary directory to cache directory!").arg(current.toString()); 257 | xDebug() << tr("Moved sources to cache directory"); 258 | xInfo() << tr("Installed package %1").arg(current.toString()); 259 | return true; 260 | } catch(SourcePluginException &e) { 261 | const auto str = tr("Failed to get sources for package%1with error:"); 262 | if(mustWork) 263 | xCritical() << str.arg(tr(" %1 ").arg(current.toString())) << e.what(); 264 | else 265 | xWarning() << str.arg(tr(" ")) << e.what(); 266 | return false; 267 | } 268 | } 269 | 270 | void InstallCommand::createSrcInclude(const QpmxDevDependency ¤t, const QpmxFormat &format, const Command::CacheLock &lock) 271 | { 272 | Q_ASSERT(lock.isLocked()); 273 | auto sDir = srcDir(current); 274 | auto bDir = buildDir(QStringLiteral("src"), current, true); 275 | 276 | QFile srcPriFile{bDir.absoluteFilePath(QStringLiteral("include.pri"))}; 277 | if(srcPriFile.exists()) { 278 | qDebug() << "source include.pri already exists. Skipping generation"; 279 | return; 280 | } 281 | 282 | if(!srcPriFile.open(QIODevice::WriteOnly | QIODevice::Text)) 283 | throw tr("Failed to open src_include.pri with error: %1").arg(srcPriFile.errorString()); 284 | QTextStream stream{&srcPriFile}; 285 | stream << "!contains(QPMX_INCLUDE_GUARDS, \"" << current.package << "\") {\n\n" 286 | << "\tQPMX_INCLUDE_GUARDS += \"" << current.package << "\"\n" 287 | << "\t#dependencies\n"; 288 | for(auto dep : format.dependencies) { 289 | // replace alias 290 | replaceAlias(dep, _aliases); 291 | // add dep 292 | auto depDir = buildDir(QStringLiteral("src"), dep); 293 | stream << "\tinclude(" << bDir.relativeFilePath(depDir.absoluteFilePath(QStringLiteral("include.pri"))) << ")\n"; 294 | } 295 | auto srcIncPri = sDir.absoluteFilePath(format.priFile); 296 | if(!devMode()) 297 | srcIncPri = bDir.relativeFilePath(srcIncPri); 298 | stream << "\t#sources\n" 299 | << "\tinclude(" << srcIncPri << ")\n" 300 | << "}\n"; 301 | stream.flush(); 302 | srcPriFile.close(); 303 | xInfo() << tr("Generated source include.pri"); 304 | } 305 | 306 | void InstallCommand::verifyDeps(const QList &list, const QpmxDevDependency &base) const 307 | { 308 | if(list.isEmpty()) 309 | throw tr("Unable to find any provider for package %1").arg(base.toString()); 310 | else if(list.size() > 1) { 311 | QStringList provList; 312 | provList.reserve(list.size()); 313 | for(const auto &data : qAsConst(list)) 314 | provList.append(data.provider); 315 | throw tr("Found more then one provider for package %1. Providers are: %2") 316 | .arg(base.toString(), provList.join(tr(", "))); 317 | } 318 | } 319 | 320 | void InstallCommand::detectDeps(const QpmxFormat &format) 321 | { 322 | for(auto dep : format.dependencies) { 323 | // replace aliases 324 | replaceAlias(dep, _aliases); 325 | // check if needed 326 | auto dIndex = -1; 327 | do { 328 | dIndex = _pkgList.indexOf(dep, dIndex + 1); 329 | if(dIndex != -1 && _pkgList[dIndex].version == dep.version) { //fine here, as dependencies do not trigger "duplicate" warnings 330 | xDebug() << tr("Skipping dependency %1 as it is already in the install list").arg(dep.toString()); 331 | break; 332 | } 333 | } while(dIndex != -1); 334 | 335 | if(dIndex == -1) { 336 | xDebug() << tr("Detected dependency to install: %1").arg(dep.toString()); 337 | _pkgList.append(dep); 338 | } 339 | } 340 | } 341 | --------------------------------------------------------------------------------