├── qdep ├── __init__.py ├── internal │ ├── __init__.py │ ├── private.py │ ├── common.py │ ├── cli.py │ └── prf.py └── qdep.py ├── tests ├── .gitignore ├── project │ ├── external │ │ ├── libstatic │ │ │ ├── static.txt │ │ │ ├── super-static.qrc │ │ │ ├── extra │ │ │ │ └── extra_sta.h │ │ │ ├── libstatic.h │ │ │ ├── libstatic.cpp │ │ │ └── libstatic.pro │ │ ├── libdynamic │ │ │ ├── extra │ │ │ │ └── extra_dyn.h │ │ │ ├── libdynamic.cpp │ │ │ ├── libdynamic.h │ │ │ └── libdynamic.pro │ │ ├── external.pro │ │ └── app │ │ │ ├── main.cpp │ │ │ └── app.pro │ ├── mixdeps │ │ ├── lib3 │ │ │ ├── lib3.cpp │ │ │ ├── lib3.h │ │ │ └── lib3.pro │ │ ├── lib1 │ │ │ ├── lib1.h │ │ │ ├── lib1.cpp │ │ │ └── lib1.pro │ │ ├── lib2 │ │ │ ├── lib2.cpp │ │ │ ├── lib2.h │ │ │ └── lib2.pro │ │ ├── app │ │ │ ├── main.cpp │ │ │ └── app.pro │ │ └── mixdeps.pro │ ├── .qmake.conf │ ├── libs │ │ ├── app │ │ │ ├── main.cpp │ │ │ └── app.pro │ │ └── libs.pro │ ├── project.pro │ ├── commands │ │ ├── commands.pro │ │ └── test-commands.py │ ├── basic │ │ ├── main.cpp │ │ └── basic.pro │ ├── single │ │ ├── single_de.ts │ │ ├── single_ja.ts │ │ ├── single.pro │ │ └── main.cpp │ ├── tests.h │ └── testrun.pri ├── packages │ ├── external │ │ ├── package2 │ │ │ ├── package2.txt │ │ │ ├── package2.qrc │ │ │ ├── staticclass.h │ │ │ ├── package2.pri │ │ │ └── staticclass.cpp │ │ ├── package1 │ │ │ └── package1.pri │ │ ├── package3 │ │ │ └── package3.pri │ │ ├── package4 │ │ │ └── package4.pri │ │ └── package5 │ │ │ ├── dynamicclass.h │ │ │ ├── package5.pri │ │ │ └── dynamicclass.cpp │ ├── basic │ │ ├── package4 │ │ │ └── package4.pri │ │ ├── package5 │ │ │ └── package5.pri │ │ ├── package1 │ │ │ ├── simple.cpp │ │ │ ├── package1.pri │ │ │ ├── package1_de.ts │ │ │ ├── package1_en.ts │ │ │ └── simple.h │ │ ├── package2 │ │ │ └── package2.pri │ │ └── package3 │ │ │ └── package3.pri │ ├── libs │ │ ├── project1 │ │ │ ├── project1.cpp │ │ │ ├── project1.h │ │ │ └── project1.pro │ │ ├── project3 │ │ │ ├── project3.h │ │ │ ├── project3.cpp │ │ │ └── project3.pro │ │ └── project2 │ │ │ ├── project2.cpp │ │ │ ├── project2.h │ │ │ └── project2.pro │ └── mixdeps │ │ ├── project2 │ │ ├── project2.cpp │ │ ├── project2.h │ │ └── project2.pro │ │ ├── project3 │ │ ├── project3.cpp │ │ ├── project3.h │ │ └── project3.pro │ │ ├── project4 │ │ ├── project4.cpp │ │ ├── project4.h │ │ └── project4.pro │ │ ├── project1 │ │ ├── project1.cpp │ │ ├── project1.h │ │ └── project1.pro │ │ └── project5 │ │ ├── project5.cpp │ │ ├── project5.h │ │ └── project5.pro ├── tests.pro ├── qdep.pro ├── testentry.py ├── setup-docker.sh ├── setup-appveyor.bat ├── setup-travis.sh └── testrun.sh ├── appveyor.yml ├── .gitignore ├── setup.py ├── .travis.yml ├── LICENSE └── README.md /qdep/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /qdep/internal/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | .qdep 2 | -------------------------------------------------------------------------------- /tests/project/external/libstatic/static.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/packages/external/package2/package2.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/packages/basic/package4/package4.pri: -------------------------------------------------------------------------------- 1 | CONFIG += package4_included 2 | -------------------------------------------------------------------------------- /tests/packages/basic/package5/package5.pri: -------------------------------------------------------------------------------- 1 | CONFIG += package5_included 2 | -------------------------------------------------------------------------------- /tests/project/mixdeps/lib3/lib3.cpp: -------------------------------------------------------------------------------- 1 | #include "lib3.h" 2 | 3 | void Lib3::lib3() 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /tests/project/.qmake.conf: -------------------------------------------------------------------------------- 1 | load(qt_build_config) 2 | CONFIG -= create_prl link_prl 3 | CONFIG += c++17 -------------------------------------------------------------------------------- /tests/packages/external/package1/package1.pri: -------------------------------------------------------------------------------- 1 | CONFIG += package1_included 2 | DEFINES += PACKAGE1_DEFINED 3 | -------------------------------------------------------------------------------- /tests/packages/libs/project1/project1.cpp: -------------------------------------------------------------------------------- 1 | #include "project1.h" 2 | 3 | void Project1::doStuff() 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /tests/packages/mixdeps/project2/project2.cpp: -------------------------------------------------------------------------------- 1 | #include "project2.h" 2 | 3 | void Project2::doStuff() 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /tests/packages/mixdeps/project3/project3.cpp: -------------------------------------------------------------------------------- 1 | #include "project3.h" 2 | 3 | void Project3::doStuff() 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /tests/packages/mixdeps/project4/project4.cpp: -------------------------------------------------------------------------------- 1 | #include "project4.h" 2 | 3 | void Project4::doStuff() 4 | { 5 | } 6 | -------------------------------------------------------------------------------- /tests/tests.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += project 4 | 5 | prepareRecursiveTarget(run-tests) 6 | QMAKE_EXTRA_TARGETS += run-tests 7 | -------------------------------------------------------------------------------- /tests/qdep.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += tests 4 | 5 | prepareRecursiveTarget(run-tests) 6 | QMAKE_EXTRA_TARGETS += run-tests lrelease 7 | -------------------------------------------------------------------------------- /tests/packages/external/package2/package2.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | package2.txt 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/project/external/libstatic/super-static.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | static.txt 4 | 5 | 6 | -------------------------------------------------------------------------------- /tests/testentry.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | 4 | from qdep.internal.cli import main 5 | 6 | if __name__ == '__main__': 7 | sys.exit(main()) 8 | -------------------------------------------------------------------------------- /tests/packages/mixdeps/project1/project1.cpp: -------------------------------------------------------------------------------- 1 | #include "project1.h" 2 | #include 3 | 4 | void Project1::doStuff() 5 | { 6 | Project2::doStuff(); 7 | } 8 | -------------------------------------------------------------------------------- /tests/packages/mixdeps/project5/project5.cpp: -------------------------------------------------------------------------------- 1 | #include "project5.h" 2 | #include 3 | 4 | void Project5::doStuff() 5 | { 6 | Project3::doStuff(); 7 | } 8 | -------------------------------------------------------------------------------- /tests/project/mixdeps/lib1/lib1.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB1_H 2 | #define LIB1_H 3 | 4 | class Lib1 5 | { 6 | public: 7 | static void lib1(); 8 | }; 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /tests/project/mixdeps/lib3/lib3.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB3_H 2 | #define LIB3_H 3 | 4 | class Lib3 5 | { 6 | public: 7 | static void lib3(); 8 | }; 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /tests/packages/libs/project1/project1.h: -------------------------------------------------------------------------------- 1 | #ifndef PROJECT1_H 2 | #define PROJECT1_H 3 | 4 | namespace Project1 { 5 | 6 | void doStuff(); 7 | 8 | } 9 | 10 | #endif -------------------------------------------------------------------------------- /tests/packages/libs/project3/project3.h: -------------------------------------------------------------------------------- 1 | #ifndef PROJECT3_H 2 | #define PROJECT3_H 3 | 4 | namespace Project3 { 5 | 6 | void doStuff(); 7 | 8 | } 9 | 10 | #endif -------------------------------------------------------------------------------- /tests/packages/external/package3/package3.pri: -------------------------------------------------------------------------------- 1 | CONFIG += package3_included 2 | DEFINES += PACKAGE3_DEFINED 3 | 4 | FUNNY_STUFF += baum42 5 | QDEP_VAR_EXPORTS += FUNNY_STUFF 6 | -------------------------------------------------------------------------------- /tests/packages/mixdeps/project1/project1.h: -------------------------------------------------------------------------------- 1 | #ifndef PROJECT1_H 2 | #define PROJECT1_H 3 | 4 | namespace Project1 { 5 | 6 | void doStuff(); 7 | 8 | } 9 | 10 | #endif -------------------------------------------------------------------------------- /tests/packages/mixdeps/project2/project2.h: -------------------------------------------------------------------------------- 1 | #ifndef PROJECT2_H 2 | #define PROJECT2_H 3 | 4 | namespace Project2 { 5 | 6 | void doStuff(); 7 | 8 | } 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /tests/packages/mixdeps/project4/project4.h: -------------------------------------------------------------------------------- 1 | #ifndef PROJECT4_H 2 | #define PROJECT4_H 3 | 4 | namespace Project4 { 5 | 6 | void doStuff(); 7 | 8 | } 9 | 10 | #endif 11 | -------------------------------------------------------------------------------- /tests/packages/external/package4/package4.pri: -------------------------------------------------------------------------------- 1 | CONFIG += package4_included 2 | DEFINES += PACKAGE4_DEFINED 3 | 4 | FUNNY_STUFF += baum24 5 | QDEP_VAR_EXPORTS += FUNNY_STUFF 6 | 7 | INCLUDEPATH += $$PWD 8 | -------------------------------------------------------------------------------- /tests/project/mixdeps/lib1/lib1.cpp: -------------------------------------------------------------------------------- 1 | #include "lib1.h" 2 | #include 3 | #include 4 | 5 | void Lib1::lib1() 6 | { 7 | Project1::doStuff(); 8 | Project3::doStuff(); 9 | } 10 | -------------------------------------------------------------------------------- /tests/project/external/libdynamic/extra/extra_dyn.h: -------------------------------------------------------------------------------- 1 | #ifndef EXTRA_DYN_H 2 | #define EXTRA_DYN_H 3 | 4 | #include 5 | 6 | template 7 | T dummy_dyn(T &&data) { return std::move(data); } 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /tests/project/external/libstatic/extra/extra_sta.h: -------------------------------------------------------------------------------- 1 | #ifndef EXTRA_STA_H 2 | #define EXTRA_STA_H 3 | 4 | #include 5 | 6 | template 7 | T dummy_sta(T &&data) { return std::move(data); } 8 | 9 | #endif 10 | -------------------------------------------------------------------------------- /tests/packages/libs/project3/project3.cpp: -------------------------------------------------------------------------------- 1 | #include "project3.h" 2 | #include 3 | 4 | void Project3::doStuff() 5 | { 6 | Simple simple; 7 | simple.value = QStringLiteral("TREE42"); 8 | simple.transform(); 9 | } 10 | -------------------------------------------------------------------------------- /tests/packages/basic/package1/simple.cpp: -------------------------------------------------------------------------------- 1 | #include "simple.h" 2 | 3 | QString Simple::transform() const 4 | { 5 | return value.toLower(); 6 | } 7 | 8 | QString Simple::translate() const 9 | { 10 | return tr("Hello World"); 11 | } 12 | -------------------------------------------------------------------------------- /tests/packages/basic/package2/package2.pri: -------------------------------------------------------------------------------- 1 | CONFIG += package2_included 2 | 3 | QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/basic/package3/package3.pri 4 | QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/basic/package4/package4.pri 5 | -------------------------------------------------------------------------------- /tests/packages/basic/package3/package3.pri: -------------------------------------------------------------------------------- 1 | CONFIG += package3_included 2 | 3 | QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/basic/package4/package4.pri 4 | QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/basic/package5/package5.pri 5 | -------------------------------------------------------------------------------- /tests/project/mixdeps/lib2/lib2.cpp: -------------------------------------------------------------------------------- 1 | #include "lib2.h" 2 | #include 3 | #include 4 | #include 5 | 6 | void Lib2::lib2() 7 | { 8 | Lib3::lib3(); 9 | Project4::doStuff(); 10 | Project5::doStuff(); 11 | } 12 | -------------------------------------------------------------------------------- /tests/project/libs/app/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char **argv) 6 | { 7 | QCoreApplication app{argc, argv}; 8 | Project2::doStuff(); 9 | 10 | return 0; 11 | } 12 | -------------------------------------------------------------------------------- /tests/packages/external/package2/staticclass.h: -------------------------------------------------------------------------------- 1 | #ifndef STATICCLASS_H 2 | #define STATICCLASS_H 3 | 4 | #include 5 | 6 | class PACKAGE2_EXPORT StaticClass 7 | { 8 | public: 9 | static bool startupRun(); 10 | 11 | int magicNumber(); 12 | }; 13 | 14 | #endif -------------------------------------------------------------------------------- /tests/packages/external/package5/dynamicclass.h: -------------------------------------------------------------------------------- 1 | #ifndef DYNAMICCLASS_H 2 | #define DYNAMICCLASS_H 3 | 4 | #include 5 | 6 | class PACKAGE5_EXPORT DynamicClass 7 | { 8 | public: 9 | static bool startupRun(); 10 | int magicNumber(); 11 | }; 12 | 13 | #endif -------------------------------------------------------------------------------- /tests/project/project.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | CONFIG += ordered 4 | 5 | SUBDIRS += \ 6 | single \ 7 | basic \ 8 | external \ 9 | libs \ 10 | mixdeps \ 11 | commands 12 | 13 | prepareRecursiveTarget(run-tests) 14 | QMAKE_EXTRA_TARGETS += run-tests lrelease 15 | -------------------------------------------------------------------------------- /tests/project/external/libstatic/libstatic.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBSTATIC_H 2 | #define LIBSTATIC_H 3 | 4 | #include 5 | 6 | class LibStatic : public StaticClass 7 | { 8 | public: 9 | static bool libStartupRun(); 10 | 11 | double magicFraction(); 12 | }; 13 | 14 | #endif -------------------------------------------------------------------------------- /tests/project/mixdeps/app/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main(int argc, char **argv) 7 | { 8 | QCoreApplication app{argc, argv}; 9 | Lib1::lib1(); 10 | Lib2::lib2(); 11 | 12 | return 0; 13 | } 14 | -------------------------------------------------------------------------------- /tests/project/external/external.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | CONFIG += ordered 4 | 5 | SUBDIRS += \ 6 | libstatic \ 7 | libdynamic \ 8 | app 9 | 10 | app.depends += libstatic libdynamic 11 | 12 | prepareRecursiveTarget(run-tests) 13 | QMAKE_EXTRA_TARGETS += run-tests 14 | -------------------------------------------------------------------------------- /tests/packages/libs/project2/project2.cpp: -------------------------------------------------------------------------------- 1 | #include "project2.h" 2 | #include 3 | #include 4 | 5 | void Project2::doStuff() 6 | { 7 | Simple simple; 8 | simple.value = QStringLiteral("BAUM42"); 9 | simple.transform(); 10 | Project3::doStuff(); 11 | } 12 | -------------------------------------------------------------------------------- /tests/project/commands/commands.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = aux 2 | 3 | run_tests.target = run-tests 4 | win32: run_tests.commands = python 5 | run_tests.commands += $$PWD/test-commands.py $$QMAKE_QMAKE $(MAKE) 6 | local_test_run: run_tests.commands += --testrun $$TEST_RUN_ARGS 7 | QMAKE_EXTRA_TARGETS += run_tests 8 | -------------------------------------------------------------------------------- /tests/packages/basic/package1/package1.pri: -------------------------------------------------------------------------------- 1 | CONFIG += package1_included 2 | DEFINES += PACKAGE1_DEFINED 3 | 4 | HEADERS += \ 5 | $$PWD/simple.h 6 | 7 | SOURCES += \ 8 | $$PWD/simple.cpp 9 | 10 | QDEP_TRANSLATIONS += \ 11 | $$PWD/package1_de.ts \ 12 | $$PWD/package1_en.ts \ 13 | 14 | INCLUDEPATH += $$PWD 15 | -------------------------------------------------------------------------------- /tests/project/mixdeps/lib2/lib2.h: -------------------------------------------------------------------------------- 1 | #ifndef LIB2_H 2 | #define LIB2_H 3 | 4 | #include 5 | 6 | #ifdef LIB2_BUILD 7 | #define LIB2_EXPORT Q_DECL_EXPORT 8 | #else 9 | #define LIB2_EXPORT Q_DECL_IMPORT 10 | #endif 11 | 12 | class LIB2_EXPORT Lib2 13 | { 14 | public: 15 | static void lib2(); 16 | }; 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /tests/packages/libs/project1/project1.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | CONFIG += static 3 | 4 | TARGET = project1 5 | 6 | HEADERS += project1.h 7 | 8 | SOURCES += project1.cpp 9 | 10 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 11 | !load(qdep):error("Failed to load qdep feature") 12 | 13 | QMAKE_EXTRA_TARGETS += run-tests 14 | -------------------------------------------------------------------------------- /tests/setup-docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | # install dependencies 5 | $SUDO pip3 install appdirs lockfile argcomplete setuptools 6 | $SUDO pip3 uninstall -y qdep 7 | 8 | # install/prepare qdep 9 | $SUDO pip3 install -e . 10 | $SUDO qdep prfgen --qmake "/opt/qt/$QT_VER/$PLATFORM/bin/qmake" 11 | 12 | # move project file 13 | mv tests/qdep.pro ./ 14 | -------------------------------------------------------------------------------- /tests/packages/libs/project2/project2.h: -------------------------------------------------------------------------------- 1 | #ifndef PROJECT2_H 2 | #define PROJECT2_H 3 | 4 | #include 5 | 6 | #ifdef PROJECT2_BUILD 7 | #define PROJECT2_EXPORT Q_DECL_EXPORT 8 | #else 9 | #define PROJECT2_EXPORT Q_DECL_IMPORT 10 | #endif 11 | 12 | namespace Project2 { 13 | 14 | PROJECT2_EXPORT void doStuff(); 15 | 16 | } 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /tests/packages/mixdeps/project3/project3.h: -------------------------------------------------------------------------------- 1 | #ifndef PROJECT3_H 2 | #define PROJECT3_H 3 | 4 | #include 5 | 6 | #ifdef PROJECT3_BUILD 7 | #define PROJECT3_EXPORT Q_DECL_EXPORT 8 | #else 9 | #define PROJECT3_EXPORT Q_DECL_IMPORT 10 | #endif 11 | 12 | namespace Project3 { 13 | 14 | PROJECT3_EXPORT void doStuff(); 15 | 16 | } 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /tests/packages/mixdeps/project5/project5.h: -------------------------------------------------------------------------------- 1 | #ifndef PROJECT5_H 2 | #define PROJECT5_H 3 | 4 | #include 5 | 6 | #ifdef PROJECT5_BUILD 7 | #define PROJECT5_EXPORT Q_DECL_EXPORT 8 | #else 9 | #define PROJECT5_EXPORT Q_DECL_IMPORT 10 | #endif 11 | 12 | namespace Project5 { 13 | 14 | PROJECT5_EXPORT void doStuff(); 15 | 16 | } 17 | 18 | #endif 19 | -------------------------------------------------------------------------------- /tests/project/basic/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | int main(int argc, char **argv) 7 | { 8 | QCoreApplication app{argc, argv}; 9 | 10 | Simple simple; 11 | simple.value = "Hello World"; 12 | COMPARE(simple.transform(), QStringLiteral("hello world")); 13 | return 0; 14 | } 15 | -------------------------------------------------------------------------------- /tests/packages/external/package5/package5.pri: -------------------------------------------------------------------------------- 1 | CONFIG += package5_included 2 | DEFINES += PACKAGE5_DEFINED 3 | 4 | QDEP_PACKAGE_EXPORTS += PACKAGE5_EXPORT 5 | !qdep_build: DEFINES += "PACKAGE5_EXPORT=" 6 | 7 | QDEP_HOOK_FNS += dynamicclass_startup_hook 8 | 9 | HEADERS += $$PWD/dynamicclass.h 10 | 11 | SOURCES += $$PWD/dynamicclass.cpp 12 | 13 | INCLUDEPATH += $$PWD 14 | -------------------------------------------------------------------------------- /tests/packages/mixdeps/project3/project3.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | 3 | TARGET = project3 4 | 5 | HEADERS += project3.h 6 | 7 | SOURCES += project3.cpp 8 | 9 | DEFINES += PROJECT3_BUILD 10 | 11 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 12 | !load(qdep):error("Failed to load qdep feature") 13 | 14 | QMAKE_EXTRA_TARGETS += run-tests 15 | -------------------------------------------------------------------------------- /tests/project/single/single_de.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GLOBAL 6 | 7 | 8 | Hello Tree 9 | Hallo Baum 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/project/single/single_ja.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | GLOBAL 6 | 7 | 8 | Hello Tree 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/packages/basic/package1/package1_de.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Simple 6 | 7 | 8 | Hello World 9 | Hallo Welt 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/project/external/libdynamic/libdynamic.cpp: -------------------------------------------------------------------------------- 1 | #include "libdynamic.h" 2 | 3 | namespace { 4 | 5 | bool _startup_run3 = false; 6 | 7 | } 8 | 9 | void libdynamic_startup_hook() 10 | { 11 | _startup_run3 = true; 12 | } 13 | 14 | bool LibDynamic::libStartupRun() 15 | { 16 | return _startup_run3; 17 | } 18 | 19 | double LibDynamic::magicFraction() 20 | { 21 | return 0.24; 22 | } 23 | -------------------------------------------------------------------------------- /tests/packages/basic/package1/package1_en.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Simple 6 | 7 | 8 | Hello World 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/packages/mixdeps/project2/project2.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | CONFIG += static 3 | 4 | TARGET = project2 5 | 6 | HEADERS += project2.h 7 | 8 | SOURCES += project2.cpp 9 | 10 | QDEP_DEFINES += PROJECT2_DEFINED 11 | 12 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 13 | !load(qdep):error("Failed to load qdep feature") 14 | 15 | QMAKE_EXTRA_TARGETS += run-tests 16 | -------------------------------------------------------------------------------- /tests/packages/mixdeps/project4/project4.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | CONFIG += static 3 | 4 | TARGET = project4 5 | 6 | HEADERS += project4.h 7 | 8 | SOURCES += project4.cpp 9 | 10 | QDEP_DEFINES += PROJECT4_DEFINED 11 | 12 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 13 | !load(qdep):error("Failed to load qdep feature") 14 | 15 | QMAKE_EXTRA_TARGETS += run-tests 16 | -------------------------------------------------------------------------------- /tests/packages/external/package2/package2.pri: -------------------------------------------------------------------------------- 1 | CONFIG += package2_included 2 | DEFINES += PACKAGE2_DEFINED 3 | 4 | QDEP_PACKAGE_EXPORTS += PACKAGE2_EXPORT 5 | !qdep_build: DEFINES += "PACKAGE2_EXPORT=" 6 | 7 | QDEP_HOOK_FNS += staticclass_startup_hook 8 | 9 | HEADERS += $$PWD/staticclass.h 10 | 11 | SOURCES += $$PWD/staticclass.cpp 12 | 13 | RESOURCES += $$PWD/package2.qrc 14 | 15 | INCLUDEPATH += $$PWD 16 | -------------------------------------------------------------------------------- /tests/project/libs/app/app.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | SOURCES += main.cpp 4 | 5 | QDEP_PROJECT_ROOT = .. 6 | # QDEP_PROJECT_ROOT = ../libs.pro 7 | QDEP_PROJECT_LINK_DEPENDS += Skycoder42/qdep@master/tests/packages/libs/project2/project2.pro 8 | 9 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 10 | !load(qdep):error("Failed to load qdep feature") 11 | 12 | include(../../testrun.pri) 13 | -------------------------------------------------------------------------------- /tests/project/external/libstatic/libstatic.cpp: -------------------------------------------------------------------------------- 1 | #include "libstatic.h" 2 | 3 | namespace { 4 | 5 | bool _startup_run2 = false; 6 | 7 | } 8 | 9 | namespace libstatic::hooks { 10 | 11 | void startup_hook() 12 | { 13 | _startup_run2 = true; 14 | } 15 | 16 | } 17 | 18 | bool LibStatic::libStartupRun() 19 | { 20 | return _startup_run2; 21 | } 22 | 23 | double LibStatic::magicFraction() 24 | { 25 | return 0.42; 26 | } 27 | -------------------------------------------------------------------------------- /tests/packages/basic/package1/simple.h: -------------------------------------------------------------------------------- 1 | #ifndef SIMPLE_H 2 | #define SIMPLE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class Simple 9 | { 10 | Q_GADGET 11 | Q_DECLARE_TR_FUNCTIONS(Simple) 12 | 13 | Q_PROPERTY(QString value MEMBER value) 14 | 15 | public: 16 | QString value; 17 | 18 | QString transform() const; 19 | QString translate() const; 20 | }; 21 | 22 | #endif -------------------------------------------------------------------------------- /tests/project/mixdeps/lib3/lib3.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | CONFIG += static 3 | 4 | QT += sql 5 | 6 | TARGET = lib3 7 | 8 | HEADERS += lib3.h 9 | 10 | SOURCES += lib3.cpp 11 | 12 | QDEP_DEFINES += LIB3_DEFINED 13 | 14 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 15 | !load(qdep):error("Failed to load qdep feature") 16 | 17 | CONFIG += no_run_tests_target 18 | include(../../testrun.pri) 19 | 20 | message(LIBS: $$LIBS) 21 | -------------------------------------------------------------------------------- /tests/project/external/libdynamic/libdynamic.h: -------------------------------------------------------------------------------- 1 | #ifndef LIBDYNAMIC_H 2 | #define LIBDYNAMIC_H 3 | 4 | #include 5 | #include 6 | 7 | #ifdef LIBDYNAMIC_BUILD 8 | #define LIBDYNAMIC_EXPORT Q_DECL_EXPORT 9 | #else 10 | #define LIBDYNAMIC_EXPORT Q_DECL_IMPORT 11 | #endif 12 | 13 | class LIBDYNAMIC_EXPORT LibDynamic : public DynamicClass 14 | { 15 | public: 16 | static bool libStartupRun(); 17 | double magicFraction(); 18 | }; 19 | 20 | #endif -------------------------------------------------------------------------------- /tests/packages/mixdeps/project5/project5.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | 3 | TARGET = project5 4 | 5 | HEADERS += project5.h 6 | 7 | SOURCES += project5.cpp 8 | 9 | DEFINES += PROJECT5_BUILD 10 | 11 | QDEP_PROJECT_DEPENDS += Skycoder42/qdep@master/tests/packages/mixdeps/project3/project3.pro 12 | 13 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 14 | !load(qdep):error("Failed to load qdep feature") 15 | 16 | QMAKE_EXTRA_TARGETS += run-tests 17 | -------------------------------------------------------------------------------- /tests/setup-appveyor.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | :: install dependencies 4 | C:\Python37-x64\Scripts\pip.exe install appdirs lockfile argcomplete setuptools || exit /B 1 5 | C:\Python37-x64\Scripts\pip.exe uninstall -y qdep || exit /B 1 6 | 7 | :: install/prepare qdep 8 | C:\Python37-x64\Scripts\pip.exe install -e . || exit /B 1 9 | C:\Python37-x64\Scripts\qdep.exe prfgen --qmake "C:\projects\Qt\%QT_VER%\%PLATFORM%\bin\qmake" || exit /B 1 10 | 11 | :: move project file 12 | move tests\qdep.pro .\dep.pro || exit /B 1 13 | -------------------------------------------------------------------------------- /tests/packages/external/package2/staticclass.cpp: -------------------------------------------------------------------------------- 1 | #include "staticclass.h" 2 | #include 3 | 4 | namespace { 5 | 6 | bool _startup_run = false; 7 | 8 | } 9 | 10 | void staticclass_startup_hook() 11 | { 12 | _startup_run = true; 13 | } 14 | 15 | #ifndef QDEP_BUILD 16 | Q_COREAPP_STARTUP_FUNCTION(libstatic_startup_hook) 17 | #endif 18 | 19 | bool StaticClass::startupRun() 20 | { 21 | return _startup_run; 22 | } 23 | 24 | int StaticClass::magicNumber() 25 | { 26 | return 422; 27 | } 28 | -------------------------------------------------------------------------------- /tests/packages/external/package5/dynamicclass.cpp: -------------------------------------------------------------------------------- 1 | #include "dynamicclass.h" 2 | #include 3 | 4 | namespace { 5 | 6 | bool _startup_run4 = false; 7 | 8 | } 9 | 10 | void dynamicclass_startup_hook() 11 | { 12 | _startup_run4 = true; 13 | } 14 | 15 | #ifndef QDEP_BUILD 16 | Q_COREAPP_STARTUP_FUNCTION(dynamicclass_startup_hook) 17 | #endif 18 | 19 | bool DynamicClass::startupRun() 20 | { 21 | return _startup_run4; 22 | } 23 | 24 | int DynamicClass::magicNumber() 25 | { 26 | return 425; 27 | } 28 | -------------------------------------------------------------------------------- /tests/packages/mixdeps/project1/project1.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | CONFIG += static 3 | 4 | TARGET = project1 5 | 6 | HEADERS += project1.h 7 | 8 | SOURCES += project1.cpp 9 | 10 | QDEP_PROJECT_DEPENDS += Skycoder42/qdep@master/tests/packages/mixdeps/project2/project2.pro 11 | 12 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 13 | !load(qdep):error("Failed to load qdep feature") 14 | 15 | qdeb_build:!contains(DEFINES, PROJECT2_DEFINED): error("!PROJECT2_DEFINED") 16 | 17 | QMAKE_EXTRA_TARGETS += run-tests 18 | -------------------------------------------------------------------------------- /tests/packages/libs/project3/project3.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | CONFIG += static 3 | 4 | TARGET = project3 5 | 6 | HEADERS += project3.h 7 | 8 | SOURCES += project3.cpp 9 | 10 | QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/basic/package1/package1.pri 11 | QDEP_EXPORTS += Skycoder42/qdep@master/tests/packages/basic/package1/package1.pri 12 | 13 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 14 | !load(qdep):error("Failed to load qdep feature") 15 | 16 | qdep_build:!package1_included: error("!package1_included") 17 | 18 | QMAKE_EXTRA_TARGETS += run-tests 19 | -------------------------------------------------------------------------------- /tests/setup-travis.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | currDir=$(dirname $0) 5 | 6 | if [[ $TRAVIS_OS_NAME == "linux" ]]; then 7 | # append post build script 8 | mv qtmodules-travis/ci/linux/build-docker.sh qtmodules-travis/ci/linux/build-docker.sh.bkp 9 | cat "$currDir/setup-docker.sh" > qtmodules-travis/ci/linux/build-docker.sh 10 | cat qtmodules-travis/ci/linux/build-docker.sh.bkp >> qtmodules-travis/ci/linux/build-docker.sh 11 | rm qtmodules-travis/ci/linux/build-docker.sh.bkp 12 | chmod a+x qtmodules-travis/ci/linux/build-docker.sh 13 | else 14 | export SUDO=sudo 15 | $currDir/setup-docker.sh 16 | fi 17 | -------------------------------------------------------------------------------- /tests/project/libs/libs.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | QDEP_PROJECT_SUBDIRS += Skycoder42/qdep@master/tests/packages/libs/project1/project1.pro 4 | QDEP_PROJECT_SUBDIRS += Skycoder42/qdep@master/tests/packages/libs/project2/project2.pro 5 | 6 | SUBDIRS += app 7 | 8 | app.qdep_depends += Skycoder42/qdep@master/tests/packages/libs/project2/project2.pro 9 | 10 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 11 | !load(qdep):error("Failed to load qdep feature") 12 | 13 | CONFIG += no_run_tests_target 14 | prepareRecursiveTarget(run-tests) 15 | include(../testrun.pri) 16 | 17 | message($$SUBDIRS) 18 | -------------------------------------------------------------------------------- /tests/project/mixdeps/lib1/lib1.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | CONFIG += static 3 | 4 | TARGET = lib1 5 | 6 | HEADERS += lib1.h 7 | 8 | SOURCES += lib1.cpp 9 | 10 | QDEP_PROJECT_ROOT = .. 11 | QDEP_PROJECT_LINK_DEPENDS += Skycoder42/qdep@master/tests/packages/mixdeps/project1/project1.pro 12 | QDEP_PROJECT_LINK_DEPENDS += Skycoder42/qdep@master/tests/packages/mixdeps/project3/project3.pro 13 | 14 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 15 | !load(qdep):error("Failed to load qdep feature") 16 | 17 | CONFIG += no_run_tests_target 18 | include(../../testrun.pri) 19 | 20 | message(LIBS: $$LIBS) 21 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: 2 | - Visual Studio 2017 3 | 4 | version: build-{build} 5 | 6 | environment: 7 | QT_VER: 5.13.0 8 | MAKE_RUN_TESTS: true 9 | TARGET_NAME: qdep 10 | 11 | matrix: 12 | - PLATFORM: msvc2017_64 13 | - PLATFORM: mingw73_64 14 | - PLATFORM: winrt_x64_msvc2017 15 | - PLATFORM: msvc2017 16 | - PLATFORM: mingw73_32 17 | - PLATFORM: winrt_x86_msvc2017 18 | - PLATFORM: winrt_armv7_msvc2017 19 | 20 | install: 21 | - git clone https://github.com/Skycoder42/QtModules.git .\qtmodules-travis 22 | - .\qtmodules-travis\ci\win\setup.bat 23 | 24 | build_script: 25 | - .\tests\setup-appveyor.bat 26 | - .\qtmodules-travis\ci\win\build.bat 27 | -------------------------------------------------------------------------------- /tests/packages/libs/project2/project2.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | 3 | TARGET = project2 4 | 5 | HEADERS += project2.h 6 | 7 | SOURCES += project2.cpp 8 | 9 | DEFINES += PROJECT2_BUILD 10 | 11 | QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/basic/package1/package1.pri # provided via project3 12 | QDEP_PROJECT_DEPENDS += Skycoder42/qdep@master/tests/packages/libs/project3/project3.pro 13 | 14 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 15 | !load(qdep):error("Failed to load qdep feature") 16 | 17 | qdep_build:package1_included: error("package1_included") 18 | qdeb_build:contains(DEFINES, PACKAGE1_DEFINED): error("!PACKAGE1_DEFINED") 19 | 20 | QMAKE_EXTRA_TARGETS += run-tests 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | *.slo 3 | *.lo 4 | *.o 5 | *.a 6 | *.la 7 | *.lai 8 | *.so 9 | *.dll 10 | *.dylib 11 | 12 | # Qt-es 13 | object_script.*.Release 14 | object_script.*.Debug 15 | *_plugin_import.cpp 16 | /.qmake.cache 17 | /.qmake.stash 18 | *.pro.user 19 | *.pro.user.* 20 | *.qbs.user 21 | *.qbs.user.* 22 | *.moc 23 | moc_*.cpp 24 | moc_*.h 25 | qrc_*.cpp 26 | ui_*.h 27 | *.qmlc 28 | *.jsc 29 | Makefile* 30 | *build-* 31 | 32 | # Qt unit tests 33 | target_wrapper.* 34 | 35 | # QtCreator 36 | *.autosave 37 | 38 | # QtCreator Qml 39 | *.qmlproject.user 40 | *.qmlproject.user.* 41 | 42 | # QtCreator CMake 43 | CMakeLists.txt.user* 44 | 45 | .idea 46 | 47 | __pycache__ 48 | *.egg-info 49 | dist 50 | build 51 | -------------------------------------------------------------------------------- /tests/project/mixdeps/app/app.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | SOURCES += main.cpp 4 | 5 | QDEP_LINK_DEPENDS += ../lib1 ../lib2 6 | 7 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 8 | !load(qdep):error("Failed to load qdep feature") 9 | 10 | !contains(DEFINES, LIB3_DEFINED): error("!LIB3_DEFINED") 11 | !contains(DEFINES, PROJECT4_DEFINED): error("!PROJECT4_DEFINED") 12 | !contains(QT, sql): error("sql") 13 | 14 | static { 15 | !contains(LIBS, -llib3): error("!-llib3") 16 | !contains(LIBS, -lproject4): error("!-lproject4") 17 | } else { 18 | contains(LIBS, -llib3): error("-llib3") 19 | contains(LIBS, -lproject4): error("-lproject4") 20 | } 21 | 22 | include(../../testrun.pri) 23 | 24 | message(LIBS: $$LIBS) 25 | -------------------------------------------------------------------------------- /tests/project/tests.h: -------------------------------------------------------------------------------- 1 | #ifndef TESTS_H 2 | #define TESTS_H 3 | 4 | #include 5 | 6 | #define COMPARE_IMPL(x, y, file, line) do { \ 7 | if((x) != (y)) { \ 8 | qCritical().nospace() << file << ":" << line << ": COMPARE ERROR: " \ 9 | << #x << "{" << (x) << "}" << " != " << #y << "{" << (y) << "}"; \ 10 | return 1; \ 11 | } \ 12 | } while(false) 13 | 14 | #define COMPARE(x, y) COMPARE_IMPL(x, y, __FILE__, __LINE__) 15 | 16 | #define VERIFY_IMPL(x, file, line) do { \ 17 | if(!(x)) { \ 18 | qCritical().nospace() << file << ":" << line << ": VERIFY ERROR: !" << #x; \ 19 | return 1; \ 20 | } \ 21 | } while(false); 22 | 23 | #define VERIFY(x) VERIFY_IMPL(x, __FILE__, __LINE__) 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /tests/project/single/single.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | CONFIG += lrelease 4 | 5 | SOURCES += \ 6 | main.cpp 7 | 8 | #CONFIG += qdep_no_qm_combine 9 | 10 | #REPO_BASE = file:///home/sky/Programming/QtLibraries/qdep/.git 11 | REPO_BASE = Skycoder42/qdep 12 | 13 | QDEP_DEPENDS += $${REPO_BASE}@master/tests/packages/basic/package1/package1.pri 14 | 15 | TRANSLATIONS += \ 16 | single_de.ts \ 17 | single_ja.ts 18 | 19 | force_ts|CONFIG(release, debug|release): DEFINES += WITH_TRANSLATIONS 20 | 21 | QM_FILES_INSTALL_PATH = $$[QT_INSTALL_TRANSLATIONS] 22 | 23 | !load(qdep):error("Failed to load qdep feature") 24 | 25 | DEFINES += "\"TS_DIR=\\\"$$__qdep_lrelease_real_dir\\\"\"" 26 | 27 | include(../testrun.pri) 28 | 29 | !package1_included: error("!package1_included") 30 | -------------------------------------------------------------------------------- /tests/project/mixdeps/lib2/lib2.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | 3 | TARGET = lib2 4 | 5 | HEADERS += lib2.h 6 | 7 | SOURCES += lib2.cpp 8 | 9 | DEFINES += LIB2_BUILD 10 | 11 | QDEP_LINK_DEPENDS += ../lib3 12 | 13 | QDEP_PROJECT_ROOT = .. 14 | QDEP_PROJECT_LINK_DEPENDS += Skycoder42/qdep@master/tests/packages/mixdeps/project4/project4.pro 15 | QDEP_PROJECT_LINK_DEPENDS += Skycoder42/qdep@master/tests/packages/mixdeps/project5/project5.pro 16 | 17 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 18 | !load(qdep):error("Failed to load qdep feature") 19 | 20 | !contains(DEFINES, LIB3_DEFINED): error("!LIB3_DEFINED") 21 | !contains(DEFINES, PROJECT4_DEFINED): error("!PROJECT4_DEFINED") 22 | !contains(QT, sql): error("sql") 23 | 24 | CONFIG += no_run_tests_target 25 | include(../../testrun.pri) 26 | 27 | message(LIBS: $$LIBS) 28 | -------------------------------------------------------------------------------- /tests/project/basic/basic.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | SOURCES += \ 4 | main.cpp 5 | 6 | #REPO_BASE = file:///home/sky/Programming/QtLibraries/qdep/.git 7 | REPO_BASE = Skycoder42/qdep 8 | 9 | QDEP_DEPENDS += $${REPO_BASE}@master/tests/packages/basic/package1/package1.pri 10 | QDEP_DEPENDS += $${REPO_BASE}@master/tests/packages/basic/package2/package2.pri 11 | QDEP_DEPENDS += $${REPO_BASE}@master/tests/packages/basic/package1/package1.pri 12 | 13 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 14 | !load(qdep):error("Failed to load qdep feature") 15 | 16 | include(../testrun.pri) 17 | 18 | !package1_included: error("!package1_included") 19 | !package2_included: error("!package2_included") 20 | !package3_included: error("!package3_included") 21 | !package4_included: error("!package4_included") 22 | !package5_included: error("!package5_included") 23 | -------------------------------------------------------------------------------- /tests/project/external/libdynamic/libdynamic.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | 3 | TARGET = dynamic 4 | 5 | DEFINES += LIBDYNAMIC_BUILD 6 | 7 | QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/external/package4/package4.pri 8 | QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/external/package5/package5.pri 9 | 10 | QDEP_HOOK_FNS += libdynamic_startup_hook 11 | 12 | QDEP_DEFINES += LIBDYNAMIC_TEST 13 | QDEP_INCLUDEPATH += $$PWD/extra 14 | 15 | HEADERS += libdynamic.h 16 | 17 | SOURCES += libdynamic.cpp 18 | 19 | #CONFIG += qdep_export 20 | CONFIG += qdep_export_all 21 | 22 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 23 | !load(qdep):error("Failed to load qdep feature") 24 | 25 | CONFIG += no_run_tests_target 26 | include(../../testrun.pri) 27 | 28 | !package4_included: error("!package4_included") 29 | !package5_included: error("!package5_included") 30 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | from qdep.qdep import version 3 | 4 | with open("README.md", "r") as fh: 5 | long_description = fh.read() 6 | 7 | setuptools.setup( 8 | name="qdep", 9 | version=version, 10 | author="Skycoder42", 11 | author_email="skycoder42.de@gmx.de", 12 | description="A very basic yet simple to use dependency management tool for qmake based projects", 13 | long_description=long_description, 14 | long_description_content_type="text/markdown", 15 | url="https://github.com/Skycoder42/qdep", 16 | packages=setuptools.find_packages(), 17 | install_requires=[ 18 | "appdirs", 19 | "lockfile", 20 | "argcomplete" 21 | ], 22 | classifiers=[ 23 | "Programming Language :: Python :: 3", 24 | "License :: OSI Approved :: BSD License", 25 | "Operating System :: OS Independent", 26 | ], 27 | entry_points={ 28 | "console_scripts": [ 29 | 'qdep=qdep.internal.cli:main' 30 | ] 31 | } 32 | ) 33 | -------------------------------------------------------------------------------- /tests/project/external/app/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace { 12 | bool startuped = false; 13 | } 14 | 15 | void app_startup_hook() 16 | { 17 | startuped = true; 18 | } 19 | 20 | int main(int argc, char **argv) 21 | { 22 | QCoreApplication app{argc, argv}; 23 | 24 | VERIFY(StaticClass::startupRun()); 25 | VERIFY(DynamicClass::startupRun()); 26 | VERIFY(LibStatic::libStartupRun()); 27 | VERIFY(LibDynamic::libStartupRun()); 28 | VERIFY(startuped); 29 | 30 | LibStatic stat; 31 | COMPARE(stat.magicNumber(), 422); 32 | COMPARE(stat.magicFraction(), 0.42); 33 | 34 | LibDynamic dyna; 35 | COMPARE(dyna.magicNumber(), 425); 36 | COMPARE(dyna.magicFraction(), 0.24); 37 | 38 | COMPARE(dummy_sta(42), 42); 39 | COMPARE(dummy_dyn(42), 42); 40 | 41 | VERIFY(QFile::exists(":/package2.txt")); 42 | VERIFY(QFile::exists(":/static.txt")); 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /tests/project/external/libstatic/libstatic.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = lib 2 | CONFIG += static 3 | 4 | TARGET = super-static 5 | 6 | debug_and_release:CONFIG(release, debug|release): DESTDIR = libout/release 7 | else:debug_and_release:CONFIG(debug, debug|release): DESTDIR = libout/debug 8 | else: DESTDIR = libout 9 | 10 | HEADERS += libstatic.h 11 | 12 | SOURCES += libstatic.cpp 13 | 14 | RESOURCES += \ 15 | super-static.qrc 16 | 17 | QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/external/package2/package2.pri 18 | QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/external/package3/package3.pri 19 | 20 | QDEP_HOOK_FNS += libstatic::hooks::startup_hook 21 | 22 | QDEP_DEFINES += LIBSTATIC_TEST 23 | QDEP_INCLUDEPATH += $$PWD/extra 24 | 25 | QDEP_EXPORTS += Skycoder42/qdep@master/tests/packages/external/package2/package2.pri 26 | 27 | CONFIG += qdep_link_private 28 | 29 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 30 | !load(qdep):error("Failed to load qdep feature") 31 | 32 | CONFIG += no_run_tests_target 33 | include(../../testrun.pri) 34 | 35 | !package2_included: error("!package2_included") 36 | !package3_included: error("!package3_included") 37 | -------------------------------------------------------------------------------- /tests/project/single/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | int main(int argc, char **argv) 9 | { 10 | QCoreApplication app{argc, argv}; 11 | #ifdef WITH_TRANSLATIONS 12 | auto translator = new QTranslator{&app}; 13 | auto ts_ok = translator->load(QLocale{QLocale::German, QLocale::Germany}, 14 | QStringLiteral("single"), 15 | QStringLiteral("_"), 16 | QStringLiteral(TS_DIR)); 17 | VERIFY(ts_ok); 18 | VERIFY(QCoreApplication::installTranslator(translator)); 19 | 20 | Simple simple; 21 | simple.value = QCoreApplication::translate("GLOBAL", "Hello Tree"); 22 | COMPARE(simple.transform(), QLatin1String("hallo baum")); 23 | COMPARE(simple.translate(), QLatin1String("Hallo Welt")); 24 | #else 25 | Simple simple; 26 | simple.value = QCoreApplication::translate("GLOBAL", "Hello Tree"); 27 | COMPARE(simple.transform(), QLatin1String("hello tree")); 28 | COMPARE(simple.translate(), QLatin1String("Hello World")); 29 | #endif 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | services: 4 | - docker 5 | 6 | sudo: required 7 | dist: trusty 8 | osx_image: xcode10.2 9 | 10 | env: 11 | global: 12 | - QDEP_CACHE_DIR=$HOME/.qdep-cache 13 | - QT_VER=5.13.0 14 | - DOCKER_IMAGE=base 15 | - MAKE_RUN_TESTS=true 16 | - TARGET_NAME=qdep 17 | 18 | matrix: 19 | include: 20 | - os: linux 21 | env: 22 | - PLATFORM=gcc_64 23 | - os: linux 24 | env: 25 | - PLATFORM=android_arm64_v8a 26 | - os: linux 27 | env: 28 | - PLATFORM=android_x86_64 29 | - os: linux 30 | env: 31 | - PLATFORM=android_armv7 32 | - os: linux 33 | env: 34 | - PLATFORM=android_x86 35 | - os: osx 36 | env: 37 | - PLATFORM=clang_64 38 | - os: osx 39 | env: 40 | - PLATFORM=ios 41 | 42 | before_install: 43 | - git clone https://github.com/Skycoder42/QtModules.git ./qtmodules-travis 44 | - travis_wait 40 ./qtmodules-travis/ci/$TRAVIS_OS_NAME/setup.sh 45 | 46 | script: 47 | - ./tests/setup-travis.sh 48 | - ./qtmodules-travis/ci/$TRAVIS_OS_NAME/build.sh 49 | 50 | before_cache: 51 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 52 | - rm -rf $HOME/.gradle/caches/*/plugin-resolution/ 53 | cache: 54 | directories: 55 | - $HOME/.gradle/caches/ 56 | - $HOME/.gradle/wrapper/ 57 | - $HOME/.android/build-cache 58 | -------------------------------------------------------------------------------- /tests/project/external/app/app.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | SOURCES += \ 4 | main.cpp 5 | 6 | QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/external/package1/package1.pri 7 | QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/external/package3/package3.pri 8 | QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/external/package5/package5.pri 9 | 10 | QDEP_HOOK_FNS += app_startup_hook 11 | 12 | QDEP_LINK_DEPENDS += ../libstatic/libstatic.pro $$OUT_PWD/../libdynamic 13 | #CONFIG += qdep_no_link 14 | 15 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 16 | !load(qdep):error("Failed to load qdep feature") 17 | 18 | include(../../testrun.pri) 19 | 20 | !package1_included: error("!package1_included") 21 | package2_included: error("package2_included") 22 | package3_included: error("package3_included") 23 | package4_included: error("package4_included") 24 | package5_included: error("package5_included") 25 | 26 | !contains(DEFINES, PACKAGE1_DEFINED):error("!PACKAGE1_DEFINED") 27 | !contains(DEFINES, PACKAGE2_DEFINED):error("!PACKAGE2_DEFINED") 28 | contains(DEFINES, PACKAGE3_DEFINED):error("PACKAGE3_DEFINED") 29 | !contains(DEFINES, PACKAGE4_DEFINED):error("!PACKAGE4_DEFINED") 30 | !contains(DEFINES, PACKAGE5_DEFINED):error("!PACKAGE5_DEFINED") 31 | 32 | !contains(FUNNY_STUFF, baum42):error("QDEP_VAR_EXPORTS failed") 33 | !contains(FUNNY_STUFF, baum24):error("QDEP_VAR_EXPORTS failed") 34 | -------------------------------------------------------------------------------- /tests/project/mixdeps/mixdeps.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | QDEP_PROJECT_SUBDIRS += Skycoder42/qdep@master/tests/packages/mixdeps/project1/project1.pro 4 | QDEP_PROJECT_SUBDIRS += Skycoder42/qdep@master/tests/packages/mixdeps/project3/project3.pro 5 | QDEP_PROJECT_SUBDIRS += Skycoder42/qdep@master/tests/packages/mixdeps/project4/project4.pro 6 | QDEP_PROJECT_SUBDIRS += Skycoder42/qdep@master/tests/packages/mixdeps/project5/project5.pro 7 | 8 | SUBDIRS += lib1 lib2 lib3 app 9 | 10 | lib1.qdep_depends += Skycoder42/qdep@master/tests/packages/mixdeps/project1/project1.pro 11 | lib1.qdep_depends += Skycoder42/qdep@master/tests/packages/mixdeps/project3/project3.pro 12 | lib2.depends += lib3 13 | lib2.qdep_depends += Skycoder42/qdep@master/tests/packages/mixdeps/project4/project4.pro 14 | lib2.qdep_depends += Skycoder42/qdep@master/tests/packages/mixdeps/project5/project5.pro 15 | app.depends += lib1 lib2 16 | 17 | CONFIG += qdep_no_pull # disable for performance - still enabled in first test 18 | !load(qdep):error("Failed to load qdep feature") 19 | 20 | CONFIG += no_run_tests_target 21 | prepareRecursiveTarget(run-tests) 22 | include(../testrun.pri) 23 | 24 | message($$SUBDIRS) 25 | 26 | # Project dependency structure: 27 | # app 28 | # |--lib1(s) 29 | # | |--project1(s) 30 | # | | |--project2(s) 31 | # | |--project3(d) 32 | # |--lib2(d) 33 | # | |--lib3(s) 34 | # | |--project4(s) 35 | # | |--project5(d) 36 | # | | |--project3(d) 37 | -------------------------------------------------------------------------------- /tests/testrun.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # $1 build path 3 | # $2 qmake path 4 | # $3 test commands only 5 | set -ex 6 | 7 | SCRIPT_PATH="$(readlink -f "$(dirname "$0")")" 8 | TEST_PATH="$1" 9 | BUILD_PATH="$1/project" 10 | CMD_PATH="$1/cmd" 11 | shift 12 | QMAKE="${1:-qmake}" 13 | shift || true 14 | COMMANDS="$1" 15 | shift || true 16 | 17 | mkdir -p "$TEST_PATH/testgitroot/Skycoder42" 18 | if [ ! -e "$TEST_PATH/testgitroot/Skycoder42/qdep" ]; then 19 | ln -s "$(realpath "$SCRIPT_PATH/..")" "$TEST_PATH/testgitroot/Skycoder42/qdep" 20 | fi 21 | 22 | export PYTHONPATH="$(realpath "$SCRIPT_PATH/.."):$PYTHONPATH" 23 | export QDEP_CACHE_DIR="$TEST_PATH/qdep-cache" 24 | export QDEP_DEFAULT_PKG_FN="$TEST_PATH/testgitroot/{}/.git" 25 | 26 | "$SCRIPT_PATH/testentry.py" prfgen --qmake "$QMAKE" 27 | 28 | if [ -z "$COMMANDS" ]; then 29 | rm -rf "$BUILD_PATH" 30 | mkdir -p "$BUILD_PATH" 31 | cd "$BUILD_PATH" 32 | "$QMAKE" "CONFIG+=local_test_run" "$SCRIPT_PATH/project/" 33 | make qmake_all 34 | make 35 | 36 | make INSTALL_ROOT="$BUILD_PATH/install" install 37 | [ -e "$BUILD_PATH/install$($QMAKE -query QT_INSTALL_TRANSLATIONS)/single_de.qm" ] 38 | [ -e "$BUILD_PATH/install$($QMAKE -query QT_INSTALL_TRANSLATIONS)/single_ja.qm" ] 39 | 40 | export LD_LIBRARY_PATH="$BUILD_PATH/external/libdynamic/:$LD_LIBRARY_PATH" 41 | make run-tests 42 | else 43 | mkdir -p "$CMD_PATH" 44 | cd "$CMD_PATH" 45 | expanded=$@ 46 | "$QMAKE" "CONFIG+=local_test_run" "TEST_RUN_ARGS=$expanded" "$SCRIPT_PATH/project/commands" 47 | make run-tests 48 | fi 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, 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 | -------------------------------------------------------------------------------- /tests/project/testrun.pri: -------------------------------------------------------------------------------- 1 | CONFIG += console 2 | CONFIG -= app_bundle 3 | 4 | HEADERS += $$PWD/tests.h 5 | INCLUDEPATH += $$PWD 6 | 7 | !no_run_tests_target:!isEmpty(OUT_PWD) { 8 | win32:!ReleaseBuild:!DebugBuild { 9 | runtarget.target = run-tests 10 | runtarget.CONFIG = recursive 11 | runtarget.recurse_target = run-tests 12 | QMAKE_EXTRA_TARGETS += runtarget 13 | } else { 14 | oneshell.target = .ONESHELL 15 | QMAKE_EXTRA_TARGETS += oneshell 16 | 17 | LIB_DIRS = $$OUT_PWD 18 | LIB_DIRS_SHELL = $$shell_path($$OUT_PWD) 19 | prefix = "-L" 20 | for(lib_arg, LIBS):equals(prefix, $$str_member($$lib_arg, 0, 1)) { 21 | lib_arg = $$str_member($$lib_arg, 2, -1) 22 | LIB_DIRS += $$lib_arg 23 | LIB_DIRS_SHELL += $$shell_path($$lib_arg) 24 | } 25 | message("Detected library paths as: $$LIB_DIRS") 26 | message("Detected shell library paths as: $$LIB_DIRS_SHELL") 27 | 28 | win32:!win32-g++ { 29 | CONFIG(debug, debug|release): outdir_helper = debug 30 | CONFIG(release, debug|release): outdir_helper = release 31 | runtarget.target = run-tests 32 | runtarget.depends += $(DESTDIR_TARGET) 33 | runtarget.commands += set PATH=$$join(LIB_DIRS_SHELL, ";");$$shell_path($$[QT_INSTALL_BINS]);$(PATH) 34 | runtarget.commands += $$escape_expand(\\n\\t)set QT_PLUGIN_PATH=$$shadowed($$dirname(_QMAKE_CONF_))/plugins;$(QT_PLUGIN_PATH) 35 | runtarget.commands += $$escape_expand(\\n\\t)set QML2_IMPORT_PATH=$$shadowed($$dirname(_QMAKE_CONF_))/qml;$(QML2_IMPORT_PATH) 36 | runtarget.commands += $$escape_expand(\\n\\t)if exist $${outdir_helper}\\fail del $${outdir_helper}\\fail 37 | runtarget.commands += $$escape_expand(\\n\\t)start /w call $(DESTDIR_TARGET) 2^> $${outdir_helper}\\test.log ^|^| echo FAIL ^> $${outdir_helper}\\fail ^& exit 0 38 | runtarget.commands += $$escape_expand(\\n\\t)type $${outdir_helper}\\test.log 39 | runtarget.commands += $$escape_expand(\\n\\t)if exist $${outdir_helper}\\fail exit 42 40 | QMAKE_EXTRA_TARGETS += runtarget 41 | } else { 42 | win32-g++: QMAKE_DIRLIST_SEP = ";" 43 | runtarget.commands += export PATH=\"$$join(LIB_DIRS_SHELL, ":"):$$shell_path($$[QT_INSTALL_BINS]):$${LITERAL_DOLLAR}$${LITERAL_DOLLAR}PATH\" 44 | runtarget.commands += $$escape_expand(\\n\\t)export QT_PLUGIN_PATH=\"$$shadowed($$dirname(_QMAKE_CONF_))/plugins/$${QMAKE_DIRLIST_SEP}$(QT_PLUGIN_PATH)\" 45 | runtarget.commands += $$escape_expand(\\n\\t)export QML2_IMPORT_PATH=\"$$shadowed($$dirname(_QMAKE_CONF_))/qml/$${QMAKE_DIRLIST_SEP}$(QML2_IMPORT_PATH)\" 46 | win32-g++: QMAKE_DIRLIST_SEP = ":" 47 | 48 | linux|win32-g++ { 49 | runtarget.commands += $$escape_expand(\\n\\t)export LD_LIBRARY_PATH=\"$$join(LIB_DIRS, "$${QMAKE_DIRLIST_SEP}$$")$${QMAKE_DIRLIST_SEP}$$[QT_INSTALL_LIBS]$${QMAKE_DIRLIST_SEP}$(LD_LIBRARY_PATH)\" 50 | runtarget.commands += $$escape_expand(\\n\\t)export QT_QPA_PLATFORM=minimal 51 | } else:mac { 52 | runtarget.commands += $$escape_expand(\\n\\t)export DYLD_LIBRARY_PATH=\"$$join(LIB_DIRS, ":"):$$[QT_INSTALL_LIBS]:$(DYLD_LIBRARY_PATH)\" 53 | runtarget.commands += $$escape_expand(\\n\\t)export DYLD_FRAMEWORK_PATH=\"$$join(LIB_DIRS, ":"):$$[QT_INSTALL_LIBS]:$(DYLD_FRAMEWORK_PATH)\" 54 | } 55 | 56 | runtarget.target = run-tests 57 | win32-g++ { 58 | runtarget.depends += $(DESTDIR_TARGET) 59 | runtarget.commands += $$escape_expand(\\n\\t)./$(DESTDIR_TARGET) 60 | } else { 61 | runtarget.depends += $(TARGET) 62 | runtarget.commands += $$escape_expand(\\n\\t)./$(TARGET) 63 | } 64 | QMAKE_EXTRA_TARGETS += runtarget 65 | } 66 | } 67 | } else: QMAKE_EXTRA_TARGETS += run-tests 68 | 69 | # qdep stats 70 | message("TARGET: $$TARGET") 71 | !qdep_build:error("qdep was loaded, but qdep_build config is not set") 72 | !isEmpty(__QDEP_REAL_DEPS_STACK):error("__QDEP_REAL_DEPS_STACK not empty: $$__QDEP_REAL_DEPS_STACK") 73 | message("__QDEP_INCLUDE_CACHE:") 74 | for(hash, __QDEP_INCLUDE_CACHE) { 75 | message(" $${hash}.package: $$eval($${hash}.package)") 76 | message(" $${hash}.version: $$eval($${hash}.version)") 77 | message(" $${hash}.path: $$eval($${hash}.path)") 78 | message(" $${hash}.exports: $$eval($${hash}.exports)") 79 | message(" $${hash}.local: $$eval($${hash}.local)") 80 | message(" $${hash}.target: $$eval($${hash}.target)") 81 | message(" $${hash}.file: $$eval($${hash}.file)") 82 | message(" $${hash}.depends: $$eval($${hash}.depends)") 83 | } 84 | message("QDEP_DEFINES: $$QDEP_DEFINES") 85 | message("DEFINES: $$DEFINES") 86 | message("QDEP_INCLUDEPATH: $$QDEP_INCLUDEPATH") 87 | message("INCLUDEPATH: $$INCLUDEPATH") 88 | message("__QDEP_PRIVATE_VARS_EXPORT: $$__QDEP_PRIVATE_VARS_EXPORT") 89 | -------------------------------------------------------------------------------- /qdep/internal/private.py: -------------------------------------------------------------------------------- 1 | from qdep.internal.common import * 2 | 3 | 4 | def dephash(*packages, project=False, pkgpath=False): 5 | for package in packages: 6 | pkg_url, pkg_branch, pkg_path = package_resolve(package, project=project) 7 | if pkgpath: 8 | print(pkg_hash(pkg_url, pkg_path) + ";" + pkg_path) 9 | else: 10 | print(pkg_hash(pkg_url, pkg_path)) 11 | 12 | 13 | def pkgresolve(package, latest_version=None, project=False, pull=True, clone=True): 14 | ov_map = get_override_map() 15 | 16 | pkg_url, pkg_branch, pkg_path = package_resolve(package, pkg_version=latest_version, project=project) 17 | needs_cache = pkg_branch is None 18 | if pkg_url in ov_map: 19 | pkg_base = ov_map[pkg_url] 20 | else: 21 | pkg_base, pkg_branch = get_sources(pkg_url, pkg_branch, pull=pull, clone=clone) 22 | print(pkg_hash(pkg_url, pkg_path)) 23 | print(pkg_branch) 24 | print(pkg_base) 25 | print(pkg_path) 26 | print(needs_cache) 27 | 28 | 29 | def hookgen(prefix, header, resources=None, hooks=None): 30 | if resources is None: 31 | resources = [] 32 | if hooks is None: 33 | hooks = [] 34 | 35 | inc_guard = cpp_escape(path.basename(header)).upper() 36 | with open(header, "w") as out_file: 37 | out_file.write("#ifndef {}\n".format(inc_guard)) 38 | out_file.write("#define {}\n\n".format(inc_guard)) 39 | 40 | out_file.write("#include \n\n") 41 | 42 | for hook in hooks: 43 | out_file.write(declare_hook(hook)) 44 | 45 | out_file.write("\ninline void qdep_{}_init() {{\n".format(cpp_escape(prefix))) 46 | out_file.write("\t// resources\n") 47 | for resource in resources: # ignore the first argument, as it is always the current pro file (to ensure this rule is always run 48 | out_file.write("\tQ_INIT_RESOURCE({});\n".format(cpp_escape(path.splitext(path.basename(resource))[0]))) 49 | out_file.write("\t// hooks\n") 50 | for hook in hooks: 51 | out_file.write("\t::{}();\n".format(hook)) 52 | out_file.write("}\n\n") 53 | 54 | out_file.write("#endif //{}\n".format(inc_guard)) 55 | 56 | 57 | def hookimp(outfile, headers=None, hooks=None): 58 | if headers is None: 59 | headers = [] 60 | if hooks is None: 61 | hooks = [] 62 | 63 | with open(outfile, "w") as out_file: 64 | targets = [] 65 | target_regex = re.compile(r".*qdep_(.*)_hooks\.h$") 66 | 67 | out_file.write("#include \n") 68 | for header in headers: 69 | abs_path = path.abspath(header) 70 | out_file.write("#include \"{}\"\n".format(abs_path)) 71 | targets.append(re.match(target_regex, header).group(1)) 72 | 73 | out_file.write("\n") 74 | for hook in hooks: 75 | out_file.write(declare_hook(hook)) 76 | 77 | out_file.write("\nnamespace {\n\n") 78 | out_file.write("void __qdep_startup_hooks() {\n") 79 | for target in targets: 80 | out_file.write("\tqdep_{}_init();\n".format(cpp_escape(target))) 81 | for hook in hooks: 82 | out_file.write("\t::{}();\n".format(hook)) 83 | out_file.write("}\n\n") 84 | out_file.write("}\n") 85 | out_file.write("Q_COREAPP_STARTUP_FUNCTION(__qdep_startup_hooks)\n") 86 | 87 | 88 | def lconvert(tsfile, outfile, *combine_files, lconvert_args=None): 89 | if lconvert_args is None: 90 | lconvert_args = ["lconvert"] 91 | 92 | # sort combine args into a map of the languages 93 | sub_map = dict() 94 | for sub_arg in combine_files: 95 | sub_base = path.splitext(path.basename(sub_arg))[0] 96 | sub_args = sub_base.split("_")[1:] 97 | for arg_cnt in range(len(sub_args)): 98 | suffix = "_".join(sub_args[arg_cnt:]) 99 | if suffix not in sub_map: 100 | sub_map[suffix] = [] 101 | sub_map[suffix].append(sub_arg) 102 | 103 | # find the qm files to combine with the input 104 | target_base = path.splitext(path.basename(tsfile))[0] 105 | ts_args = target_base.split("_")[1:] 106 | combine_list = [] 107 | for arg_cnt in range(len(ts_args) - 1, -1, -1): 108 | suffix = "_".join(ts_args[arg_cnt:]) 109 | if suffix in sub_map: 110 | combine_list = combine_list + sub_map[suffix] 111 | combine_list.append(tsfile) 112 | 113 | # run lconvert 114 | sub_run(lconvert_args + ["-if", "ts", "-i"] + combine_list + ["-of", "ts", "-o", outfile], check=True) 115 | 116 | 117 | def prolink(prodir, pkghash, pkgpath, link=None): 118 | link_target = path.join(prodir, ".qdep", pkghash, "src") 119 | pro_target = path.join(link_target, pkgpath[1:]) 120 | 121 | # skip mode -> only print the path 122 | if link is None: 123 | print(pro_target) 124 | return 125 | 126 | # normal mode -> first unlink any existing stuff 127 | if path.islink(link_target): 128 | if os.name == 'nt' and path.isdir(link_target): 129 | os.rmdir(link_target) 130 | else: 131 | os.unlink(link_target) 132 | elif path.isdir(link_target): 133 | shutil.rmtree(link_target) 134 | elif path.exists(link_target): 135 | os.remove(link_target) 136 | 137 | # then link the correct project again 138 | try: 139 | os.makedirs(path.dirname(link_target), exist_ok=True) 140 | os.symlink(link, link_target, target_is_directory=True) 141 | except OSError: 142 | # symlink is not possible, copy instead 143 | print("Project WARNING: Failed to symlink project dependecy. Performing deep copy instead", file=sys.stderr) 144 | shutil.copytree(link, link_target, symlinks=True, ignore_dangling_symlinks=True) 145 | 146 | print(pro_target) 147 | -------------------------------------------------------------------------------- /qdep/qdep.py: -------------------------------------------------------------------------------- 1 | from qdep.internal.common import * 2 | from qdep.internal.prf import * 3 | 4 | 5 | version = "1.1.1" 6 | 7 | 8 | def prfgen(script_path, qmake="qmake", data_dir=None): 9 | if data_dir is not None: 10 | prf_path = data_dir 11 | else: 12 | qmake_res = sub_run([qmake, "-query", "QT_HOST_DATA"], check=True, stdout=subprocess.PIPE, encoding="UTF-8") 13 | prf_path = str(qmake_res.stdout).strip() 14 | prf_path = path.join(prf_path, "mkspecs", "features", "qdep.prf") 15 | print("Generating PRF-File as: ", prf_path) 16 | os.makedirs(path.dirname(prf_path), exist_ok=True) 17 | with open(prf_path, "w") as prf_file: 18 | prf_file.write("# Generated by {}\n".format(script_path)) 19 | prf_file.write("qdep_force|!isEmpty(OUT_PWD) { # Used to detect if qdep was loaded from an indirect evaluation\n\n") 20 | prf_file.write("isEmpty(QDEP_PATH) {\n") 21 | prf_file.write("\tQDEP_PATH = {}\n".format(script_path)) 22 | prf_file.write("\twin32: QDEP_PATH = $${QDEP_PATH}.exe\n") 23 | prf_file.write("}\n") 24 | prf_file.write("isEmpty(QDEP_VERSION): QDEP_VERSION = {}\n".format(version)) 25 | prf_file.write(qdep_prf) 26 | prf_file.write("\n}\n") 27 | 28 | 29 | def init(profile): 30 | with open(profile, "a") as pro_file: 31 | pro_file.write("\nQDEP_DEPENDS += \n\n") 32 | pro_file.write("!load(qdep):error(\"Failed to load qdep feature! Run 'qdep prfgen --qmake $$QMAKE_QMAKE' to create it.\")\n") 33 | 34 | 35 | def lupdate(pri_path, qmake="qmake", lupdate_args=None): 36 | if lupdate_args is None: 37 | lupdate_args = [] 38 | 39 | qmake_res = sub_run([qmake, "-query", "QT_HOST_BINS"], check=True, stdout=subprocess.PIPE, encoding="UTF-8") 40 | lupdate_path = path.join(str(qmake_res.stdout).strip(), "lupdate") 41 | with tempfile.TemporaryDirectory() as tmp_dir: 42 | tmp_pro_path = path.join(tmp_dir, "lupdate.pro") 43 | with open(tmp_pro_path, "w") as tmp_file: 44 | tmp_file.write("include({})\n".format(pri_path)) 45 | tmp_file.write("TRANSLATIONS = $$QDEP_TRANSLATIONS\n") 46 | sub_run([lupdate_path] + lupdate_args + [tmp_pro_path], check=True) 47 | 48 | 49 | def clear(no_confirm=False): 50 | def folder_size(path): 51 | total = 0 52 | for entry in os.scandir(path): 53 | if entry.is_file(): 54 | total += entry.stat().st_size 55 | elif entry.is_dir(): 56 | total += folder_size(entry.path) 57 | return total 58 | 59 | if not no_confirm: 60 | print("All caches sources will be removed and have to be downloaded again. Make sure no other qdep instance is currently running!") 61 | ok = input("Do you really want ro remove all cached sources? [y/N]").lower() 62 | if len(ok) == 0: 63 | return 64 | elif ok != "y" and ok != "yes": 65 | return 66 | 67 | cache_dir = os.getenv("QDEP_CACHE_DIR", get_cache_dir_default()) 68 | cache_dir = path.join(cache_dir, "src") 69 | if path.exists(cache_dir): 70 | rm_size = folder_size(cache_dir) 71 | shutil.rmtree(cache_dir) 72 | else: 73 | rm_size = 0 74 | print("Removed {} bytes".format(rm_size)) 75 | 76 | 77 | def versions(package, tags=True, branches=False, short=False, limit=None): 78 | package_url, _v, _p = package_resolve(package) 79 | 80 | if tags: 81 | pkg_tags = get_all_tags(package_url, tags=True, branches=False, allow_empty=True) 82 | if limit is not None: 83 | pkg_tags = pkg_tags[-limit:] 84 | else: 85 | pkg_tags = [] 86 | 87 | if branches: 88 | pkg_branches = get_all_tags(package_url, tags=False, branches=True, allow_empty=True) 89 | if limit is not None: 90 | pkg_branches = pkg_branches[-limit:] 91 | else: 92 | pkg_branches = [] 93 | 94 | if short: 95 | print(" ".join(pkg_branches + pkg_tags)) 96 | else: 97 | if branches: 98 | print("Branches:") 99 | if len(pkg_branches) > 0: 100 | for branch in pkg_branches: 101 | print(" " + branch) 102 | else: 103 | print(" -- No branches found --") 104 | 105 | if tags: 106 | if branches: 107 | print("") 108 | 109 | print("Tags:") 110 | if len(pkg_tags) > 0: 111 | for tag in pkg_tags: 112 | print(" " + tag) 113 | else: 114 | print(" -- No tags found --") 115 | 116 | 117 | def query(package, check=True, print_versions=False, expand=False): 118 | pkg_url, pkg_version, pkg_path = package_resolve(package) 119 | if check: 120 | if pkg_version is None: 121 | pkg_version = get_latest_tag(pkg_url, allow_empty=True, allow_error=True) 122 | pkg_exists = pkg_version is not None 123 | else: 124 | pkg_exists = pkg_version in get_all_tags(pkg_url, branches=True, tags=True, allow_empty=True, allow_error=True) 125 | else: 126 | pkg_exists = False # not knowing means false 127 | 128 | if expand: 129 | if pkg_version is None: 130 | raise Exception("Unable to determine package version") 131 | else: 132 | print("{}@{}{}".format(pkg_url, pkg_version, pkg_path)) 133 | else: 134 | print("Input:", package) 135 | if pkg_version is not None: 136 | print("Expanded Name: {}@{}{}".format(pkg_url, pkg_version, pkg_path)) 137 | else: 138 | print("Expanded Name:", None) 139 | print("URL:", pkg_url) 140 | print("Version:", pkg_version) 141 | print("Path:", pkg_path) 142 | if check: 143 | print("Exists:", pkg_exists) 144 | 145 | if print_versions: 146 | print("") 147 | versions(package, branches=True) 148 | 149 | 150 | def get(*targets, extract=False, evaluate=False, recurse=True, qmake="qmake", make="make", cache_dir=None): 151 | if cache_dir is not None: 152 | os.environ["QDEP_CACHE_DIR"] = cache_dir 153 | 154 | # eval the pro files if needed 155 | if evaluate: 156 | # special case: since we only download sources, a simple qmake run is enough, no actual "evaluation" needed 157 | for pro_file in targets: 158 | eval_pro_depends(pro_file, qmake, make) 159 | print("Done! qmake finished successfully, all qdep sources for the project tree have been downloaded.") 160 | return 161 | elif extract: 162 | packages = [] 163 | for pro_file in targets: 164 | print("Extracting dependencies from {}...".format(pro_file)) 165 | packages = packages + extract_pro_depends(pro_file, qmake) 166 | else: 167 | packages = list(targets) 168 | 169 | # download the actual sources 170 | print("Found {} initial packages".format(len(packages))) 171 | pkg_hashes = set() 172 | for package in packages: 173 | pkg_url, pkg_version, pkg_path = package_resolve(package) 174 | p_hash = pkg_hash(pkg_url, pkg_path) 175 | if p_hash in pkg_hashes: 176 | continue 177 | 178 | print("Downloading sources for {}...".format(package)) 179 | cache_dir, _b = get_sources(pkg_url, pkg_version) 180 | 181 | if recurse: 182 | print("Extracting dependencies from {}...".format(package)) 183 | new_packages = extract_pro_depends(path.join(cache_dir, pkg_path[1:]), qmake) 184 | print("Found {} dependent packages".format(len(new_packages))) 185 | for pkg in new_packages: 186 | packages.append(pkg) 187 | 188 | pkg_hashes.add(p_hash) 189 | print("Done!") 190 | 191 | 192 | def update(pro_file, evaluate=False, replace=False, qmake="qmake", make="make"): 193 | if evaluate: 194 | all_deps = invert_map(eval_pro_depends(pro_file, qmake, make, dump_depends=True)) 195 | pkg_all, pkg_new = check_for_updates(all_deps.keys()) 196 | update_files = set() 197 | for old_pkg, new_pkg in pkg_new.items(): 198 | for dep in all_deps[old_pkg]: 199 | update_files.add(dep) 200 | all_deps[new_pkg] = (all_deps[new_pkg] if new_pkg in all_deps else []) + all_deps[old_pkg] 201 | all_deps.pop(old_pkg) 202 | all_deps = invert_map(all_deps) 203 | for upd_file in update_files: 204 | replace_or_print_update(upd_file, all_deps[upd_file], pkg_new, replace) 205 | else: 206 | print("Extracting dependencies from {}...".format(pro_file)) 207 | packages = extract_pro_depends(pro_file, qmake) 208 | pkg_all, pkg_new = check_for_updates(packages) 209 | replace_or_print_update(pro_file, pkg_all, pkg_new, replace) 210 | -------------------------------------------------------------------------------- /qdep/internal/common.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import os 3 | import re 4 | import shutil 5 | import stat 6 | import subprocess 7 | import sys 8 | import tempfile 9 | from os import path 10 | from collections import OrderedDict 11 | 12 | import appdirs 13 | from lockfile import LockFile 14 | 15 | 16 | def get_cache_dir_default(): 17 | return appdirs.user_cache_dir("qdep") 18 | 19 | 20 | def get_cache_dir(pkg_url, pkg_branch): 21 | cache_dir = os.getenv("QDEP_CACHE_DIR", get_cache_dir_default()) 22 | cache_dir = path.join(cache_dir, hashlib.sha3_256(pkg_url.encode("UTF-8")).hexdigest(), pkg_branch) 23 | os.makedirs(cache_dir, exist_ok=True) 24 | return cache_dir 25 | 26 | 27 | def get_override_map(): 28 | or_env = os.getenv("QDEP_SOURCE_OVERRIDE") 29 | if or_env is None: 30 | return {} 31 | 32 | or_map = {} 33 | for env_info in or_env.split(";"): 34 | env_pair = env_info.split("^") 35 | if len(env_pair) == 2: 36 | or_map[env_pair[0]] = env_pair[1] 37 | return or_map 38 | 39 | 40 | def pkg_hash(pkg_url, pkg_path): 41 | return "__QDEP_PKG_" + hashlib.sha3_256((pkg_url + pkg_path).lower().encode("UTF-8")).hexdigest() 42 | 43 | 44 | def cpp_escape(name, ascii=True): 45 | return re.sub(r"[^\w]", "_", name, re.ASCII if ascii else re.UNICODE) 46 | 47 | 48 | def declare_hook(hook): 49 | last_sep = hook.rfind("::") 50 | if last_sep != -1: 51 | return "namespace {} {{ void {}(); }}\n".format(hook[:last_sep], hook[last_sep + 2:]) 52 | else: 53 | return "void {}();\n".format(hook) 54 | 55 | 56 | def sub_run(*args, **kwargs): 57 | sys.stdout.flush() 58 | sys.stderr.flush() 59 | return subprocess.run(*args, **kwargs) 60 | 61 | 62 | def package_resolve(package, pkg_version=None, project=False, expand=True): 63 | pattern = re.compile(r'^(?:([^@\/]+\/[^@\/]+)|(\w+:\/\/.*\.git|[^@:]*@[^@]*:[^@]+\.git))(?:@([^\/\s]+)(\/.*)?)?$') 64 | path_pattern = re.compile(r'^.*\/([^\/]+)\/?\.git$') 65 | 66 | match = re.match(pattern, package) 67 | if not match: 68 | raise Exception("Given package is not a valid package descriptor: " + package) 69 | 70 | # extract package url 71 | if match.group(1) is not None: 72 | if not expand: 73 | pkg_url = match.group(1) 74 | else: 75 | pkg_url = os.getenv("QDEP_DEFAULT_PKG_FN", "https://github.com/{}.git").format(match.group(1)) 76 | elif match.group(2) is not None: 77 | pkg_url = match.group(2) 78 | else: 79 | raise Exception("Invalid package specified: " + package) 80 | 81 | # extract branch 82 | if match.group(3) is not None: 83 | pkg_branch = match.group(3) 84 | elif pkg_version is not None: 85 | pkg_branch = pkg_version 86 | else: 87 | pkg_branch = None 88 | 89 | # extract pri path 90 | if match.group(4) is not None: 91 | pkg_path = match.group(4) 92 | elif not expand: 93 | pkg_path = "" 94 | else: 95 | pkg_path = "/" + re.match(path_pattern, pkg_url).group(1).lower() + (".pro" if project else ".pri") 96 | 97 | return pkg_url, pkg_branch, pkg_path 98 | 99 | 100 | def get_all_tags(pkg_url, branches=False, tags=True, allow_empty=False, allow_error=False): 101 | ls_args = ["git", "ls-remote", "--refs"] 102 | if not branches and tags: 103 | ls_args.append("--tags") 104 | elif not tags and branches: 105 | ls_args.append("--heads") 106 | if not allow_empty: 107 | ls_args.append("--exit-code") 108 | elif not tags and not branches: 109 | return [] # Nothing to check for 110 | ls_args.append(pkg_url) 111 | 112 | ls_res = sub_run(ls_args, check=not allow_error, stdout=subprocess.PIPE, encoding="UTF-8") 113 | ref_pattern = re.compile(r'[a-fA-F0-9]+\s+refs\/(?:tags|heads)\/([^\s]+)') 114 | tags = [] 115 | for match in re.finditer(ref_pattern, ls_res.stdout): 116 | tags.append(match.group(1)) 117 | return tags 118 | 119 | 120 | def get_latest_tag(pkg_url, allow_empty=False, allow_error=False): 121 | all_tags = get_all_tags(pkg_url, allow_empty=allow_empty, allow_error=allow_error) 122 | if len(all_tags) > 0: 123 | return all_tags[-1] 124 | else: 125 | return None 126 | 127 | 128 | def get_sources(pkg_url, pkg_branch, pull=True, clone=True): 129 | 130 | if pkg_branch is None: 131 | pkg_branch = get_latest_tag(pkg_url) 132 | 133 | cache_dir = get_cache_dir(pkg_url, pkg_branch) 134 | 135 | locker = LockFile(cache_dir) 136 | locker.acquire() 137 | try: 138 | needs_ro = False 139 | if path.isdir(path.join(cache_dir, ".git")): 140 | if pull and not path.exists(path.join(cache_dir, ".qdep_static_branch")): 141 | sub_run(["git", "pull", "--force", "--ff-only", "--update-shallow", "--recurse-submodules"], cwd=cache_dir, stdout=subprocess.DEVNULL, check=True) 142 | needs_ro = True 143 | elif clone: 144 | sub_run(["git", "clone", "--recurse-submodules", "--depth", "1", "--branch", pkg_branch, pkg_url, cache_dir], check=True) 145 | head_ref_res = sub_run(["git", "symbolic-ref", "HEAD"], cwd=cache_dir, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) 146 | if head_ref_res.returncode != 0: 147 | open(path.join(cache_dir, ".qdep_static_branch"), 'a').close() 148 | needs_ro = True 149 | else: 150 | raise Exception("The --no-clone flag was specified - cannot install new packages with pulling disabled") 151 | 152 | if needs_ro: 153 | NO_WRITE_MASK = ~stat.S_IWUSR & ~stat.S_IWGRP & ~stat.S_IWOTH 154 | for root, dirs, files in os.walk(cache_dir, topdown=True): 155 | dirs[:] = [d for d in dirs if d != ".git"] 156 | for file in files: 157 | f_path = path.join(root, file) 158 | cur_perm = stat.S_IMODE(os.lstat(f_path).st_mode) 159 | os.chmod(f_path, cur_perm & NO_WRITE_MASK) 160 | except: 161 | shutil.rmtree(cache_dir, ignore_errors=True) 162 | raise 163 | finally: 164 | locker.release() 165 | 166 | return cache_dir, pkg_branch 167 | 168 | 169 | def extract_pro_depends(pro_file, qmake): 170 | packages = [] 171 | 172 | with tempfile.TemporaryDirectory() as tmp_dir: 173 | dump_name = path.join(tmp_dir, "qdep_dummy.pro") 174 | with open(dump_name, "w") as dump_file: 175 | dump_file.write("depends += $$fromfile($$quote({}), QDEP_DEPENDS)\n".format(pro_file)) 176 | dump_file.write("depends += $$fromfile($$quote({}), QDEP_PROJECT_SUBDIRS)\n".format(pro_file)) 177 | dump_file.write("depends += $$fromfile($$quote({}), QDEP_PROJECT_LINK_DEPENDS)\n".format(pro_file)) 178 | dump_file.write("depends += $$fromfile($$quote({}), QDEP_PROJECT_DEPENDS)\n".format(pro_file)) 179 | dump_file.write("!write_file($$PWD/qdep_depends.txt, depends):error(\"write error\")\n") 180 | sub_run([qmake, dump_name], cwd=tmp_dir, check=True, stdout=subprocess.DEVNULL) 181 | with open(path.join(tmp_dir, "qdep_depends.txt"), "r") as dep_file: 182 | for line in dep_file.readlines(): 183 | packages.append(line.strip()) 184 | 185 | return packages 186 | 187 | 188 | def eval_pro_depends(pro_file, qmake, make, dump_depends=False): 189 | with tempfile.TemporaryDirectory() as tmp_dir: 190 | print("Running {} on {}...".format(qmake, pro_file)) 191 | sub_run([qmake] + (["CONFIG+=__qdep_dump_dependencies"] if dump_depends else []) + [pro_file], cwd=tmp_dir, check=True, stdout=subprocess.DEVNULL) 192 | print("Running {} qmake_all...".format(make)) 193 | sub_run([make, "qmake_all"], cwd=tmp_dir, check=True, stdout=subprocess.DEVNULL) 194 | 195 | if dump_depends: 196 | all_deps = {} 197 | for root, dirs, files in os.walk(tmp_dir, topdown=True): 198 | for file in files: 199 | if file == "qdep_depends.txt": 200 | with open(path.join(root, file), "r") as dep_list: 201 | dep_name = dep_list.readline().strip() 202 | all_deps[dep_name] = [] 203 | for dep in dep_list: 204 | all_deps[dep_name].append(dep.strip()) 205 | 206 | return all_deps 207 | else: 208 | return None 209 | 210 | 211 | def invert_map(original): 212 | inverted = OrderedDict() 213 | for key, values in original.items(): 214 | for value in values: 215 | if value not in inverted: 216 | inverted[value] = [key] 217 | else: 218 | inverted[value].append(key) 219 | return inverted 220 | 221 | 222 | def check_for_updates(packages): 223 | pkg_all = [] 224 | pkg_new = {} 225 | for package in packages: 226 | pkg_url, pkg_version, _p = package_resolve(package) 227 | if pkg_version is None: 228 | pkg_all.append(package) 229 | continue 230 | 231 | # check if the package actually has any tags 232 | all_tags = get_all_tags(pkg_url, allow_empty=True) 233 | if len(all_tags) == 0: 234 | pkg_all.append(package) 235 | continue 236 | 237 | # check if actually a tag and not a branch 238 | if pkg_version not in all_tags and pkg_version in get_all_tags(pkg_url, branches=True, tags=False, allow_empty=True): 239 | pkg_all.append(package) 240 | continue 241 | 242 | # check if latest tag has changed 243 | if all_tags[-1] != pkg_version: 244 | pkg_name, _v, pkg_path = package_resolve(package, expand=False) 245 | print("Found a new version for package {}: {} -> {}".format(pkg_name, pkg_version, all_tags[-1])) 246 | new_pkg = "{}@{}{}".format(pkg_name, all_tags[-1], pkg_path) 247 | pkg_all.append(new_pkg) 248 | pkg_new[package] = new_pkg 249 | else: 250 | pkg_all.append(package) 251 | 252 | return pkg_all, pkg_new 253 | 254 | 255 | def replace_or_print_update(pro_file, pkg_all, pkg_new, replace): 256 | if replace: 257 | with open(pro_file, "r") as in_file: 258 | with open(pro_file + ".qdepnew", "w") as out_file: 259 | for line in in_file: 260 | for src, repl in pkg_new.items(): 261 | line = line.replace(src, repl) 262 | out_file.write(line) 263 | 264 | os.remove(pro_file) 265 | os.rename(pro_file + ".qdepnew", pro_file) 266 | print("Updated dependencies in {} - please check the file to make sure it has not been corrupted!".format(pro_file)) 267 | else: 268 | print("") 269 | print("Dependencies for {}:".format(pro_file)) 270 | print("QDEP_DEPENDS =", format(" \\\n\t".join(pkg_all))) 271 | -------------------------------------------------------------------------------- /tests/project/commands/test-commands.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import os 5 | import shutil 6 | import subprocess 7 | import xml.etree.ElementTree as ET 8 | 9 | 10 | is_local_run = False 11 | tests_allowed = None 12 | qdep_path = None 13 | qmake_path = None 14 | make_path = None 15 | 16 | 17 | def exec_process(name, proc, args, keep_stdout=False, keep_stderr=False): 18 | print("Executing:", name, " ".join(args)) 19 | 20 | if "QDEP_DEFAULT_PKG_FN" in os.environ: 21 | proc_env = os.environ.copy() 22 | proc_env.pop("QDEP_DEFAULT_PKG_FN") 23 | else: 24 | proc_env = os.environ 25 | 26 | sys.stdout.flush() 27 | run_res = subprocess.run([proc] + list(args), cwd=os.getcwd(), check=True, stdout=subprocess.PIPE if keep_stdout else None, stderr=subprocess.PIPE if keep_stderr else None, encoding="UTF-8", env=proc_env) 28 | 29 | if keep_stdout: 30 | stdout_data = str(run_res.stdout) 31 | else: 32 | stdout_data = None 33 | if keep_stderr: 34 | stderr_data = str(run_res.stderr) 35 | else: 36 | stderr_data = None 37 | return stdout_data, stderr_data 38 | 39 | 40 | def exec_qdep(*args, keep_stdout=False, keep_stderr=False, trace=True): 41 | return exec_process("qdep", qdep_path, (["--trace"] if trace else []) + list(args), keep_stdout=keep_stdout, keep_stderr=keep_stderr) 42 | 43 | 44 | def exec_qmake(*args, keep_stdout=False, keep_stderr=False): 45 | return exec_process("qmake", qmake_path, args, keep_stdout=keep_stdout, keep_stderr=keep_stderr) 46 | 47 | 48 | def test_prfgen(): 49 | exec_qdep("prfgen", "-d", os.getcwd()) 50 | assert os.path.isfile("mkspecs/features/qdep.prf") 51 | with open("mkspecs/features/qdep.prf", "r") as prf_in: 52 | print("qdep.prf: ", prf_in.readline().strip()) 53 | 54 | 55 | def test_init(): 56 | with open("test.pro", "w") as pro_file: 57 | pro_file.write("TEMPLATE = aux\n") 58 | exec_qdep("init", "test.pro") 59 | with open("test.pro", "a") as pro_file: 60 | pro_file.write("!qdep_build:error(\"qdep loaded but not activated!\")\n") 61 | exec_qmake() 62 | 63 | 64 | def test_lupdate(): 65 | with open("test.pri", "w") as pri_file: 66 | pri_file.write("SOURCES += $$PWD/test.cpp\n") 67 | pri_file.write("QDEP_TRANSLATIONS += $$PWD/test_de.ts\n") 68 | with open("test.cpp", "w") as cpp_file: 69 | cpp_file.write("#include \n\n") 70 | cpp_file.write("void test() {\n") 71 | cpp_file.write("\tauto x = QCoreApplication::translate(\"GLOBAL\", \"Hello Tree\");\n") 72 | cpp_file.write("}\n") 73 | exec_qdep("lupdate", "--qmake", qmake_path, "--pri-file", os.path.abspath("test.pri"), "--", "-no-ui-lines") 74 | 75 | assert os.path.isfile("test.pri") 76 | root = ET.parse("test_de.ts").getroot() 77 | assert root[0][1][1].text == "Hello Tree" 78 | 79 | 80 | def test_versions(): 81 | def v_fetch(*args, strip=True): 82 | vs_sout, _e = exec_qdep("versions", *args, "Skycoder42/qpmx-sample-package", keep_stdout=True) 83 | if strip: 84 | vs_list = map(lambda s: s.strip(), vs_sout.strip().split("\n")) 85 | return list(vs_list) 86 | else: 87 | return vs_sout.strip().split(" ") 88 | 89 | v_res = v_fetch() 90 | assert v_res[0:5] == ["Tags:", "1.0.0", "1.0.1", "1.0.10", "1.0.11"] 91 | assert v_res[-1] == "1.2.0" 92 | 93 | v_res = v_fetch("-b", "--no-tags") 94 | assert v_res == ["Branches:", "master"] 95 | 96 | v_res = v_fetch("-b") 97 | assert v_res[0:8] == ["Branches:", "master", "", "Tags:", "1.0.0", "1.0.1", "1.0.10", "1.0.11"] 98 | assert v_res[-1] == "1.2.0" 99 | 100 | v_res = v_fetch("-b", "-s", strip=False) 101 | assert v_res[0:5] == ["master", "1.0.0", "1.0.1", "1.0.10", "1.0.11"] 102 | assert v_res[-1] == "1.2.0" 103 | 104 | v_res = v_fetch("-s", "--limit", "5", strip=False) 105 | assert v_res == ["1.0.7", "1.0.8", "1.0.9", "1.1.0", "1.2.0"] 106 | 107 | 108 | def test_query(): 109 | def q_fetch(*args, package="Skycoder42/qpmx-sample-package", keep_stderr=False): 110 | qry_sout, _e = exec_qdep("query", *args, package, keep_stdout=True, keep_stderr=keep_stderr) 111 | qry_list = map(lambda s: s.strip(), qry_sout.strip().split("\n")) 112 | return list(qry_list) 113 | 114 | q_res = q_fetch() 115 | assert q_res == [ 116 | "Input: Skycoder42/qpmx-sample-package", 117 | "Expanded Name: https://github.com/Skycoder42/qpmx-sample-package.git@1.2.0/qpmx-sample-package.pri", 118 | "URL: https://github.com/Skycoder42/qpmx-sample-package.git", 119 | "Version: 1.2.0", 120 | "Path: /qpmx-sample-package.pri", 121 | "Exists: True" 122 | ] 123 | 124 | q_res = q_fetch("--expand") 125 | assert q_res == ["https://github.com/Skycoder42/qpmx-sample-package.git@1.2.0/qpmx-sample-package.pri"] 126 | 127 | q_res = q_fetch("--no-check") 128 | assert q_res == [ 129 | "Input: Skycoder42/qpmx-sample-package", 130 | "Expanded Name: None", 131 | "URL: https://github.com/Skycoder42/qpmx-sample-package.git", 132 | "Version: None", 133 | "Path: /qpmx-sample-package.pri" 134 | ] 135 | 136 | q_res = q_fetch("--no-check", package="Skycoder42/qpmx-sample-package@1.2.0") 137 | assert q_res == [ 138 | "Input: Skycoder42/qpmx-sample-package@1.2.0", 139 | "Expanded Name: https://github.com/Skycoder42/qpmx-sample-package.git@1.2.0/qpmx-sample-package.pri", 140 | "URL: https://github.com/Skycoder42/qpmx-sample-package.git", 141 | "Version: 1.2.0", 142 | "Path: /qpmx-sample-package.pri" 143 | ] 144 | 145 | q_res = q_fetch("--versions") 146 | assert q_res[0:15] == [ 147 | "Input: Skycoder42/qpmx-sample-package", 148 | "Expanded Name: https://github.com/Skycoder42/qpmx-sample-package.git@1.2.0/qpmx-sample-package.pri", 149 | "URL: https://github.com/Skycoder42/qpmx-sample-package.git", 150 | "Version: 1.2.0", 151 | "Path: /qpmx-sample-package.pri", 152 | "Exists: True", 153 | "", 154 | "Branches:", "master", "", "Tags:", "1.0.0", "1.0.1", "1.0.10", "1.0.11" 155 | ] 156 | 157 | q_res = q_fetch(package="Skycoder42/qpmx-sample-package@9.9.9") 158 | assert q_res == [ 159 | "Input: Skycoder42/qpmx-sample-package@9.9.9", 160 | "Expanded Name: https://github.com/Skycoder42/qpmx-sample-package.git@9.9.9/qpmx-sample-package.pri", 161 | "URL: https://github.com/Skycoder42/qpmx-sample-package.git", 162 | "Version: 9.9.9", 163 | "Path: /qpmx-sample-package.pri", 164 | "Exists: False" 165 | ] 166 | 167 | q_res = q_fetch(package="file:///invalid/path/.git", keep_stderr=True) 168 | assert q_res == [ 169 | "Input: file:///invalid/path/.git", 170 | "Expanded Name: None", 171 | "URL: file:///invalid/path/.git", 172 | "Version: None", 173 | "Path: /path.pri", 174 | "Exists: False" 175 | ] 176 | 177 | 178 | def test_get(): 179 | def g_fetch(*args): 180 | sout, serr = exec_qdep("get", "--qmake", qmake_path, "--make", make_path, "--dir", os.getcwd(), *args, keep_stdout=True, keep_stderr=True) 181 | 182 | sout = sout.strip() 183 | if len(sout) == 0: 184 | sout = [] 185 | else: 186 | sout = list(map(lambda s: s.strip(), sout.strip().split("\n"))) 187 | 188 | serr = serr.strip() 189 | if len(serr) == 0: 190 | serr = [] 191 | else: 192 | serr = list(map(lambda s: s.strip(), serr.strip().split("\n"))) 193 | 194 | return sout, serr 195 | 196 | sout, serr = g_fetch("Skycoder42/qdep@master/tests/packages/basic/package1/package1.pri") 197 | assert len(sout) == 5 198 | assert len(serr) == 1 199 | 200 | sout, serr = g_fetch("--no-recurse", "Skycoder42/qdep@master/tests/packages/basic/package2/package2.pri") 201 | assert len(sout) == 3 202 | assert len(serr) == 0 203 | 204 | sout, serr = g_fetch("Skycoder42/qdep@master/tests/packages/basic/package2/package2.pri") 205 | assert len(sout) == 14 206 | assert len(serr) == 0 207 | 208 | with open("extract.pro", "w") as pro_file: 209 | pro_file.write("QDEP_DEPENDS = Skycoder42/qdep@master/tests/packages/basic/package1/package1.pri\n\n") 210 | exec_qdep("init", os.path.abspath("extract.pro")) 211 | sout, serr = g_fetch("--extract", os.path.abspath("extract.pro")) 212 | assert len(sout) == 6 213 | assert len(serr) == 0 214 | 215 | with open("eval.pro", "w") as pro_file: 216 | pro_file.write("TEMPLATE = subdirs\n") 217 | pro_file.write("SUBDIRS += sub_eval\n") 218 | os.mkdir("sub_eval") 219 | with open("sub_eval/sub_eval.pro", "w") as pro_file: 220 | pro_file.write("TEMPLATE = aux\n") 221 | pro_file.write("QDEP_DEPENDS = Skycoder42/qdep@master/tests/packages/basic/package1/package1.pri\n\n") 222 | exec_qdep("init", os.path.abspath("sub_eval/sub_eval.pro")) 223 | sout, serr = g_fetch("--eval", os.path.abspath("eval.pro")) 224 | print("qmake/make error output:\n\t", "\n\t".join(serr)) 225 | assert len(sout) == 3 226 | 227 | 228 | def test_update(): 229 | def u_run(*args, strip_eval=False): 230 | sout, _e = exec_qdep("update", "--qmake", qmake_path, "--make", make_path, *args, keep_stdout=strip_eval) 231 | if strip_eval: 232 | return list(map(lambda s: s.strip(), sout.strip().split("\n"))) 233 | else: 234 | return None 235 | 236 | with open("normal.pro", "w") as pro_file: 237 | pro_file.write("QDEP_DEPENDS += Skycoder42/qpmx-sample-package@1.0.0/qpmx-sample-package.prc\n") 238 | pro_file.write("QDEP_DEPENDS += Skycoder42/qpmx-sample-package\n") 239 | pro_file.write("QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/basic/package1/package1.pri\n\n") 240 | exec_qdep("init", os.path.abspath("normal.pro")) 241 | u_res = u_run(os.path.abspath("normal.pro"), strip_eval=True) 242 | assert len(u_res) == 7 243 | assert u_res[1] == "Found a new version for package Skycoder42/qpmx-sample-package: 1.0.0 -> 1.2.0" 244 | assert u_res[4:] == [ 245 | "QDEP_DEPENDS = Skycoder42/qpmx-sample-package@1.2.0/qpmx-sample-package.prc \\", 246 | "Skycoder42/qpmx-sample-package \\", 247 | "Skycoder42/qdep@master/tests/packages/basic/package1/package1.pri" 248 | ] 249 | 250 | u_run("--replace", os.path.abspath("normal.pro")) 251 | with open("normal.pro", "a") as pro_file: 252 | pro_file.write("write_file($$PWD/normal.txt, QDEP_DEPENDS)\n") 253 | exec_qmake("normal.pro") 254 | with open("normal.txt", "r") as txt_file: 255 | txt_lines = txt_file.readlines() 256 | assert txt_lines == [ 257 | "Skycoder42/qpmx-sample-package@1.2.0/qpmx-sample-package.prc\n", 258 | "Skycoder42/qpmx-sample-package\n", 259 | "Skycoder42/qdep@master/tests/packages/basic/package1/package1.pri\n" 260 | ] 261 | 262 | with open("eval.pro", "w") as pro_file: 263 | pro_file.write("TEMPLATE = subdirs\n") 264 | pro_file.write("SUBDIRS += sub_eval\n") 265 | os.mkdir("sub_eval") 266 | with open("sub_eval/sub_eval.pro", "w") as pro_file: 267 | pro_file.write("TEMPLATE = aux\n") 268 | pro_file.write("QDEP_DEPENDS += Skycoder42/qpmx-sample-package@1.0.0/qpmx-sample-package.prc\n") 269 | pro_file.write("QDEP_DEPENDS += Skycoder42/qpmx-sample-package\n") 270 | pro_file.write("QDEP_DEPENDS += Skycoder42/qdep@master/tests/packages/basic/package1/package1.pri\n\n") 271 | exec_qdep("init", os.path.abspath("sub_eval/sub_eval.pro")) 272 | u_res = u_run("--eval", os.path.abspath("eval.pro"), strip_eval=True) 273 | print(len(u_res), u_res) 274 | assert len(u_res) == 8 275 | assert u_res[2] == "Found a new version for package Skycoder42/qpmx-sample-package: 1.0.0 -> 1.2.0" 276 | assert u_res[5:] == [ 277 | "QDEP_DEPENDS = Skycoder42/qpmx-sample-package \\", 278 | "Skycoder42/qdep@master/tests/packages/basic/package1/package1.pri \\", 279 | "Skycoder42/qpmx-sample-package@1.2.0/qpmx-sample-package.prc" 280 | ] 281 | 282 | u_run("--eval", "--replace", os.path.abspath("eval.pro")) 283 | with open("sub_eval/sub_eval.pro", "a") as pro_file: 284 | pro_file.write("write_file($$PWD/../eval.txt, QDEP_DEPENDS)\n") 285 | exec_qmake("sub_eval/sub_eval.pro") 286 | with open("eval.txt", "r") as txt_file: 287 | txt_lines = txt_file.readlines() 288 | assert txt_lines == [ 289 | "Skycoder42/qpmx-sample-package@1.2.0/qpmx-sample-package.prc\n", 290 | "Skycoder42/qpmx-sample-package\n", 291 | "Skycoder42/qdep@master/tests/packages/basic/package1/package1.pri\n" 292 | ] 293 | 294 | 295 | def test_clear(): # TODO implement later 296 | pass 297 | 298 | 299 | def test_run(name, test_fn, **kwargs): 300 | if tests_allowed is not None and name not in tests_allowed: 301 | print("\n*** Skipping test:", name) 302 | return 303 | 304 | print("\n*** Running test:", name) 305 | os.makedirs(name) 306 | os.chdir(name) 307 | 308 | test_fn(**kwargs) 309 | 310 | os.chdir("..") 311 | print("*** SUCCESS") 312 | 313 | 314 | if __name__ == '__main__': 315 | assert len(sys.argv) > 2 316 | qmake_path = sys.argv[1] 317 | make_path = sys.argv[2] 318 | 319 | if len(sys.argv) > 3: 320 | is_local_run = True 321 | qdep_path = os.path.join(os.path.dirname(__file__), "..", "..", "testentry.py") 322 | tests_allowed = sys.argv[4:] 323 | if len(tests_allowed) == 0: 324 | tests_allowed = None 325 | else: 326 | qdep_path = shutil.which("qdep") 327 | if qdep_path is None: 328 | print("Failed to find qdep. Current path is:", os.environ["PATH"], file=sys.stderr) 329 | sys.exit(1) 330 | 331 | print("qdep path:", qdep_path) 332 | print("qmake path:", qmake_path) 333 | 334 | cwd = os.path.join(os.getcwd(), "tests") 335 | if os.path.exists(cwd): 336 | shutil.rmtree(cwd) 337 | os.makedirs(cwd) 338 | os.chdir(cwd) 339 | 340 | test_run("prfgen", test_prfgen) 341 | test_run("init", test_init) 342 | test_run("lupdate", test_lupdate) 343 | test_run("versions", test_versions) 344 | test_run("query", test_query) 345 | test_run("get", test_get) 346 | test_run("update", test_update) 347 | test_run("clear", test_clear) 348 | -------------------------------------------------------------------------------- /qdep/internal/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | import argcomplete 4 | 5 | from qdep.qdep import * 6 | from qdep.internal.private import * 7 | 8 | 9 | def complete_path(prefix, filter_fn): 10 | def map_dir_contents(base_dir, file): 11 | full_path = path.join(base_dir, file) 12 | if path.isdir(full_path): 13 | return full_path + "/" 14 | else: 15 | return full_path 16 | 17 | path_base, path_name = path.split(prefix) 18 | if len(path_base) == 0: 19 | path_base = "." 20 | return map(lambda f: map_dir_contents(path_base, f), filter(lambda f: filter_fn(path_base, path_name, f), os.listdir(path_base))) 21 | 22 | 23 | def complete_executable(prefix, limiter=None): 24 | def filter_dir_contents(base_dir, base_name, file): 25 | if path.isdir(path.join(base_dir, file)): 26 | return True 27 | elif file.startswith(base_name) and (limiter is None or limiter in file): 28 | return True 29 | else: 30 | return False 31 | 32 | return complete_path(prefix, filter_dir_contents) 33 | 34 | 35 | def complete_suffix(prefix, suffix): 36 | def filter_dir_contents(base_dir, base_name, file): 37 | if path.isdir(path.join(base_dir, file)): 38 | return True 39 | elif file.startswith(base_name) and file.endswith(suffix): 40 | return True 41 | else: 42 | return False 43 | 44 | return complete_path(prefix, filter_dir_contents) 45 | 46 | 47 | def qmake_completer(prefix, **kwargs): 48 | return complete_executable(prefix, "qmake") 49 | 50 | 51 | def make_completer(prefix, **kwargs): 52 | return complete_executable(prefix, "make") 53 | 54 | 55 | def dir_completer(prefix, **kwargs): 56 | def filter_dir_contents(base_dir, base_name, file): 57 | return path.isdir(path.join(base_dir, file)) and file.startswith(base_name) 58 | 59 | return complete_path(prefix, filter_dir_contents) 60 | 61 | 62 | def pro_completer(prefix, **kwargs): 63 | return complete_suffix(prefix, ".pro") 64 | 65 | 66 | def pri_completer(prefix, **kwargs): 67 | return complete_suffix(prefix, ".pri") 68 | 69 | 70 | def main(): 71 | parser = argparse.ArgumentParser(description="A very basic yet simple to use dependency management tool for qmake based projects.") 72 | parser.add_argument("--version", action="version", version=version) 73 | parser.add_argument("--trace", action="store_true", help="In case of an exception, print the whole stack trace") 74 | 75 | sub_args = parser.add_subparsers(dest="operation", title="Operations", metavar="{operation}") 76 | 77 | prfgen_parser = sub_args.add_parser("prfgen", help="Generate a qmake project feature (prf) for the given qmake.") 78 | prfgen_parser.add_argument("--qmake", action="store", default="qmake", help="The path to a qmake executable to place the prf file for.").completer = qmake_completer 79 | prfgen_parser.add_argument("-d", "--dir", dest="dir", action="store", help="The directory containing the mkspec folder where to place the prf file. If not specified, qmake is queried form the location.").completer = dir_completer 80 | 81 | init_parser = sub_args.add_parser("init", help="Initialize a pro file to use qdep by adding the required lines.") 82 | init_parser.add_argument("profile", help="The path to the pro file to add the qdep code to.").completer = pro_completer 83 | 84 | lupdate_parser = sub_args.add_parser("lupdate", help="Run lupdate for the QDEP_TRANSLATION variable in a given pri file.") 85 | lupdate_parser.add_argument("--qmake", action="store", default="qmake", help="The path to a qmake executable to find the corresponding lupdate for.").completer = qmake_completer 86 | lupdate_parser.add_argument("--pri-file", dest="pri_path", action="store", required=True, help="The path to the pri-file that contains a QDEP_TRANSLATIONS variable, to generate translations for.").completer = pri_completer 87 | lupdate_parser.add_argument("largs", action="store", nargs="*", metavar="lupdate-argument", help="Additionals arguments to be passed to lupdate. MUST be proceeded by '--'!") 88 | 89 | clear_parser = sub_args.add_parser("clear", help="Remove all sources from the users global cache.") 90 | clear_parser.add_argument("-y", "--yes", dest="yes", action="store_true", help="Immediatly remove the caches, without asking for confirmation first.") 91 | 92 | versions_parser = sub_args.add_parser("versions", help="List all known versions/tags of the given package") 93 | versions_parser.add_argument("-b", "--branches", dest="branches", action="store_true", help="Include branches into the output.") 94 | versions_parser.add_argument("--no-tags", dest="tags", action="store_false", help="Exclude tags from the output.") 95 | versions_parser.add_argument("-s", "--short", dest="short", action="store_true", help="Print output as a single, uncommented line") 96 | versions_parser.add_argument("--limit", action="store", type=int, help="Limit the returned lists to the LIMIT newest entries per type.") 97 | versions_parser.add_argument("package", help="The package to list the versions for. Specify without a version of pro/pri file path!") 98 | 99 | query_parser = sub_args.add_parser("query", help="Query details about a given package identifier") 100 | query_parser.add_argument("--expand", action="store_true", help="Only expand the package name, don't output anything else.") 101 | query_parser.add_argument("--no-check", dest="check", action="store_false", help="Do not check if the package actually exists.") 102 | query_parser.add_argument("--versions", action="store_true", help="Also query and display all available tags and branches. See 'qdep.py versions' for alternative formats.") 103 | query_parser.add_argument("package", help="The package to query information for.") 104 | 105 | get_parser = sub_args.add_parser("get", help="Download the sources of one ore more packages into the source cache.") 106 | get_parser.add_argument("--extract", action="store_true", help="Run in pro-file mode. Arguments are interpreted as pro files and are scanned for dependencies") 107 | get_parser.add_argument("--eval", action="store_true", help="Fully evaluate all pro files by running qmake on them. Implies '--extract'.") 108 | get_parser.add_argument("--no-recurse", dest="recurse", action="store_false", help="Do not scan downloaded packages for further dependencies.") 109 | get_parser.add_argument("--qmake", action="store", default="qmake", help="The path to a qmake executable to use for evaluation if '--eval' was specified.").completer = qmake_completer 110 | get_parser.add_argument("--make", action="store", default="make", help="The path to a make executable to use for evaluation if '--eval' was specified.").completer = make_completer 111 | get_parser.add_argument("-d", "--dir", "--cache-dir", dest="dir", action="store", help="Specify the directory where to download the sources to. Shorthand for using the QDEP_CACHE_DIR environment variable.").completer = dir_completer 112 | get_parser.add_argument("args", nargs="+", help="The packages (or pro files if using '--extract') to download the sources for.").completer = pro_completer 113 | 114 | update_parser = sub_args.add_parser("update", help="Check for newer versions of used packages and optionally update them.") 115 | update_parser.add_argument("--eval", action="store_true", help="Fully evaluate all pro files by running qmake on them.") 116 | update_parser.add_argument("--qmake", action="store", default="qmake", help="The path to a qmake executable to use for evaluation if '--eval' was specified.").completer = qmake_completer 117 | update_parser.add_argument("--make", action="store", default="make", help="The path to a make executable to use for evaluation if '--eval' was specified.").completer = make_completer 118 | update_parser.add_argument("--replace", action="store_true", help="Automatically replace newer packages in the evaluated project files instead of printing to the console.") 119 | update_parser.add_argument("profile", metavar="pro-file", help="The qmake pro-file to update dependencies for.").completer = pro_completer 120 | 121 | dephash_parser = sub_args.add_parser("dephash", help="[INTERNAL] Generated unique identifying hashes for qdep packages.") 122 | dephash_parser.add_argument("--project", action="store_true", help="Interpret input as a project dependency, not a normal pri dependency.") 123 | dephash_parser.add_argument("--pkgpath", action="store_true", help="Return the hash and the pro/pri subpath as tuple, seperated by a ';'.") 124 | dephash_parser.add_argument("input", action="store", nargs="*", metavar="package", help="The packages to generate hashes for.") 125 | 126 | pkgresolve_parser = sub_args.add_parser("pkgresolve", help="[INTERNAL] Download the given qdep package and extract relevant information from it.") 127 | pkgresolve_parser.add_argument("--no-pull", dest="pull", action="store_false", help="Do not update existing packages that are based on branches instead of tags.") 128 | pkgresolve_parser.add_argument("--no-clone", dest="clone", action="store_false", help="Do not allow installation of new packages. Trying so will lead to an error. Updating existing packages is still possible.") 129 | pkgresolve_parser.add_argument("--project", dest="project", action="store_true", help="Interpret input as a project dependency, not a normal pri dependency") 130 | pkgresolve_parser.add_argument("package", action="store", metavar="package", help="The package identifier of the package to be downloaded and resolved.") 131 | pkgresolve_parser.add_argument("version", action="store", nargs="?", metavar="latest-version", help="The previously cached version for packages with no version identifier.") 132 | 133 | hookgen_parser = sub_args.add_parser("hookgen", help="[INTERNAL] Generate a header file with a method to load all resource hooks.") 134 | hookgen_parser.add_argument("--hooks", action="store", nargs="*", help="The names of additional hook functions to be referenced.") 135 | hookgen_parser.add_argument("prefix", action="store", help="The target name to use as part of the generated hook method.") 136 | hookgen_parser.add_argument("header", action="store", help="The path to the header-file to be generated.") 137 | hookgen_parser.add_argument("dummy", action="store", metavar="pro-file", help="The path to the current pro file - needed for Makefile rules.") 138 | hookgen_parser.add_argument("resources", action="store", nargs="*", metavar="resource", help="Paths to the resource-files to generate the hooks for.") 139 | 140 | hookimp_parser = sub_args.add_parser("hookimp", help="[INTERNAL] Generate a source file that includes and runs all qdep hooks as normal startup hook.") 141 | hookimp_parser.add_argument("--hooks", action="store", nargs="*", help="The names of additional hook functions to be referenced.") 142 | hookimp_parser.add_argument("outfile", action="store", help="The path to the cpp-file to be generated.") 143 | hookimp_parser.add_argument("dummy", action="store", metavar="pro-file", help="The path to the current pro file - needed for Makefile rules.") 144 | hookimp_parser.add_argument("headers", action="store", nargs="*", metavar="header", help="Paths to the header-files that contain hooks to be run.") 145 | 146 | lconvert_parser = sub_args.add_parser("lconvert", help="[INTERNAL] Combine ts files with translations from qdep packages.") 147 | lconvert_parser.add_argument("--combine", action="store", nargs="*", help="The qdep ts files that should be combined into the real ones.") 148 | lconvert_parser.add_argument("tsfile", action="store", help="The path to the ts file to combine with the qdep ts files.") 149 | lconvert_parser.add_argument("outfile", action="store", help="The path to the ts file to be generated.") 150 | lconvert_parser.add_argument("largs", action="store", nargs="+", metavar="lconvert-tool", help="Path to the lconvert tool as well as additional arguments to it.") 151 | 152 | prolink_parser = sub_args.add_parser("prolink", help="[INTERNAL] Resolve the path a linked project dependency would be at.") 153 | prolink_parser.add_argument("prodir", action="store", help="The directory of the pro file that includes the other one.") 154 | prolink_parser.add_argument("pkghash", action="store", help="The hash identifier of the project to link.") 155 | prolink_parser.add_argument("pkgpath", action="store", help="The path to the pro file within the dependency.") 156 | prolink_parser.add_argument("--link", action="store", help="Perform the link operation and create the symlink/dirtree, based on the given path to the dependency sources.") 157 | 158 | argcomplete.autocomplete(parser) 159 | res = parser.parse_args() 160 | 161 | try: 162 | if res.operation == "prfgen": 163 | prfgen(path.abspath(sys.argv[0]), res.qmake, res.dir) 164 | elif res.operation == "init": 165 | init(res.profile) 166 | elif res.operation == "lupdate": 167 | lupdate(res.pri_path, res.qmake, res.largs) 168 | elif res.operation == "clear": 169 | clear(res.yes) 170 | elif res.operation == "versions": 171 | versions(res.package, res.tags, res.branches, res.short, res.limit) 172 | elif res.operation == "query": 173 | query(res.package, res.check, res.versions, res.expand) 174 | elif res.operation == "get": 175 | get(*res.args, extract=res.extract, evaluate=res.eval, recurse=res.recurse, qmake=res.qmake, make=res.make, cache_dir=res.dir) 176 | elif res.operation == "update": 177 | update(res.profile, res.eval, res.replace, res.qmake, res.make) 178 | elif res.operation == "dephash": 179 | dephash(*res.input, project=res.project, pkgpath=res.pkgpath) 180 | elif res.operation == "pkgresolve": 181 | pkgresolve(res.package, res.version, res.project, res.pull, res.clone) 182 | elif res.operation == "hookgen": 183 | hookgen(res.prefix, res.header, res.resources, res.hooks) 184 | elif res.operation == "hookimp": 185 | hookimp(res.outfile, res.headers, res.hooks) 186 | elif res.operation == "lconvert": 187 | lconvert(res.tsfile, res.outfile, *res.combine, lconvert_args=res.largs) 188 | elif res.operation == "prolink": 189 | prolink(res.prodir, res.pkghash, res.pkgpath, res.link) 190 | else: 191 | parser.print_help() 192 | return 1 193 | 194 | return 0 195 | except Exception as exception: 196 | if res.trace or True: 197 | raise 198 | else: 199 | print(exception, file=sys.stderr) 200 | return 1 201 | 202 | 203 | if __name__ == '__main__': 204 | sys.exit(main()) 205 | -------------------------------------------------------------------------------- /qdep/internal/prf.py: -------------------------------------------------------------------------------- 1 | qdep_prf = """isEmpty(QDEP_TOOL) QDEP_TOOL = $$system_path($$QDEP_PATH) 2 | 3 | # verify versions are correct 4 | __qdep_script_version = $$system($$QDEP_TOOL --version) 5 | !equals(QDEP_VERSION, $$__qdep_script_version):error("qdep.py script version ($$__qdep_script_version) is different to qdep.prf version ($$QDEP_VERSION)! Run '$$QDEP_TOOL prfgen --qmake $$QMAKE_QMAKE' to update the prf file!") 6 | 7 | # set some variables 8 | isEmpty(QDEP_CACHE_SCOPE): QDEP_CACHE_SCOPE = stash 9 | 10 | isEmpty(QDEP_GENERATED_DIR): QDEP_GENERATED_DIR = $$OUT_PWD 11 | debug_and_release:CONFIG(release, debug|release): QDEP_GENERATED_SOURCES_DIR = $${QDEP_GENERATED_DIR}/release 12 | else:debug_and_release:CONFIG(debug, debug|release): QDEP_GENERATED_SOURCES_DIR = $${QDEP_GENERATED_DIR}/debug 13 | else: QDEP_GENERATED_SOURCES_DIR = $$QDEP_GENERATED_DIR 14 | isEmpty(QDEP_GENERATED_TS_DIR): QDEP_GENERATED_TS_DIR = $$QDEP_GENERATED_DIR/.qdepts 15 | debug_and_release:CONFIG(release, debug|release): QDEP_GENERATED_TS_DIR = $${QDEP_GENERATED_TS_DIR}/release 16 | else:debug_and_release:CONFIG(debug, debug|release): QDEP_GENERATED_TS_DIR = $${QDEP_GENERATED_TS_DIR}/debug 17 | isEmpty(QDEP_LUPDATE) { 18 | qtPrepareTool(QDEP_LUPDATE, lupdate) 19 | QDEP_LUPDATE += -recursive -locations relative 20 | qdep_lupdate_no_obsolete: QDEP_LUPDATE += -no-obsolete 21 | } 22 | isEmpty(QDEP_LCONVERT) { 23 | qtPrepareTool(QDEP_LCONVERT, lconvert) 24 | QDEP_LCONVERT += -sort-contexts 25 | } 26 | 27 | isEmpty(QDEP_EXPORT_PATH): QDEP_EXPORT_PATH = $$QDEP_GENERATED_DIR 28 | debug_and_release:CONFIG(release, debug|release): QDEP_EXPORT_PATH = $$QDEP_EXPORT_PATH/release 29 | debug_and_release:CONFIG(debug, debug|release): QDEP_EXPORT_PATH = $$QDEP_EXPORT_PATH/debug 30 | isEmpty(QDEP_EXPORT_NAME) { 31 | __qdep_base_name = $$basename(_PRO_FILE_) 32 | QDEP_EXPORT_NAME = $$replace(__qdep_base_name, "\\\\.[^\\\\.]*$", "_export.pri") 33 | } 34 | 35 | isEmpty(__QDEP_PRIVATE_SEPERATOR): __QDEP_PRIVATE_SEPERATOR = "===" 36 | isEmpty(__QDEP_TUPLE_SEPERATOR): __QDEP_TUPLE_SEPERATOR = "---" 37 | 38 | CONFIG += qdep_build 39 | DEFINES += QDEP_BUILD 40 | 41 | # The primary dependecy collector function 42 | defineTest(qdepCollectDependencies) { 43 | # transform all dependencies into unique hashes 44 | qdep_dependencies = 45 | for(arg, ARGS): qdep_dependencies += $$system_quote($$arg) 46 | qdep_ok = 47 | qdep_hashes = $$system($$QDEP_TOOL dephash $$qdep_dependencies, lines, qdep_ok) 48 | !equals(qdep_ok, 0):return(false) 49 | 50 | for(dep_hash, qdep_hashes) { 51 | # handle each dependency, but each package only once 52 | dep_pkg = $$take_first(ARGS) 53 | !contains(__QDEP_INCLUDE_CACHE, $$dep_hash) { 54 | # Install the sources and extract some parameters 55 | dep_extra_args = 56 | qdep_no_pull: dep_extra_args += --no-pull 57 | qdep_no_clone: dep_extra_args += --no-clone 58 | !isEmpty($${dep_hash}.version): dep_vers_arg = $$system_quote($$first($${dep_hash}.version)) 59 | else: dep_vers_arg = 60 | qdep_ok = 61 | dep_data = $$system($$QDEP_TOOL pkgresolve $$dep_extra_args $$system_quote($$dep_pkg) $$dep_vers_arg, lines, qdep_ok) 62 | !equals(qdep_ok, 0):return(false) 63 | !equals(dep_hash, $$take_first(dep_data)):error("Critical internal error: dependencies out of sync"):return(false) 64 | 65 | dep_version = $$take_first(dep_data) 66 | dep_base = $$take_first(dep_data) 67 | dep_path = $$take_first(dep_data) 68 | dep_needs_cache = $$take_first(dep_data) 69 | 70 | $${dep_hash}.package = $$dep_pkg 71 | $${dep_hash}.version = $$dep_version 72 | $${dep_hash}.path = $${dep_base}$${dep_path} 73 | $${dep_hash}.exports = 74 | $${dep_hash}.local = 1 75 | export($${dep_hash}.package) 76 | export($${dep_hash}.version) 77 | export($${dep_hash}.path) 78 | export($${dep_hash}.local) 79 | 80 | # Cache the package version for dependencies with undetermined versions 81 | !qdep_no_cache:equals(dep_needs_cache, True):!cache($${dep_hash}.version, set $$QDEP_CACHE_SCOPE):warning("Failed to cache package version for $$dep_pkg") 82 | 83 | # Find all dependencies that package depends on and call this method recursively for those 84 | sub_deps = $$fromfile($$eval($${dep_hash}.path), QDEP_DEPENDS) 85 | __QDEP_REAL_DEPS_STACK += $$dep_hash 86 | !isEmpty(sub_deps):!qdepCollectDependencies($$sub_deps):return(false) 87 | __QDEP_INCLUDE_CACHE *= $$take_last(__QDEP_REAL_DEPS_STACK) 88 | export(__QDEP_INCLUDE_CACHE) 89 | 90 | # Handle all defines for symbol exports, if specified 91 | sub_exports = $$fromfile($$eval($${dep_hash}.path), QDEP_PACKAGE_EXPORTS) 92 | 93 | qdep_export_all|contains(QDEP_EXPORTS, $$dep_pkg) { 94 | !static:!staticlib:for(sub_export, sub_exports) { 95 | DEFINES += "$${sub_export}=Q_DECL_EXPORT" 96 | QDEP_EXPORTED_DEFINES += "$${sub_export}=Q_DECL_IMPORT" 97 | $${dep_hash}.exports += $$sub_export 98 | } else:for(sub_export, sub_exports) { 99 | DEFINES += "$${sub_export}=" 100 | QDEP_EXPORTED_DEFINES += "$${sub_export}=" 101 | } 102 | } else:for(sub_export, sub_exports): \\ 103 | DEFINES += "$${sub_export}=" 104 | export(DEFINES) 105 | export(QDEP_EXPORTED_DEFINES) 106 | export($${dep_hash}.exports) 107 | } else: \\ 108 | !equals($${dep_hash}.package, $$dep_pkg): \\ 109 | warning("Detected includes of multiple different versions of the same dependency. Package \\"$$first($${dep_hash}.package)\\" is used, and package \\"$$dep_pkg\\" was detected.") 110 | } 111 | 112 | return(true) 113 | } 114 | 115 | # The dependecy collector function for project dependencies in subdirs projects 116 | defineTest(qdepCollectProjectDependencies) { 117 | # transform all dependencies into unique hashes 118 | qdep_dependencies = 119 | for(arg, ARGS): qdep_dependencies += $$system_quote($$arg) 120 | qdep_ok = 121 | qdep_hashes = $$system($$QDEP_TOOL dephash --project $$qdep_dependencies, lines, qdep_ok) 122 | !equals(qdep_ok, 0):return(false) 123 | 124 | for(dep_hash, qdep_hashes) { 125 | # handle each dependency, but each package only once 126 | dep_pkg = $$take_first(ARGS) 127 | !contains(__QDEP_INCLUDE_CACHE, $$dep_hash) { 128 | # Install the sources and extract some parameters 129 | dep_extra_args = 130 | qdep_no_pull: dep_extra_args += --no-pull 131 | qdep_no_clone: dep_extra_args += --no-clone 132 | !isEmpty($${dep_hash}.version): dep_vers_arg = $$system_quote($$first($${dep_hash}.version)) 133 | else: dep_vers_arg = 134 | qdep_ok = 135 | dep_data = $$system($$QDEP_TOOL pkgresolve --project $$dep_extra_args $$system_quote($$dep_pkg) $$dep_vers_arg, lines, qdep_ok) 136 | !equals(qdep_ok, 0):return(false) 137 | !equals(dep_hash, $$take_first(dep_data)):error("Critical internal error: project dependencies out of sync"):return(false) 138 | 139 | dep_version = $$take_first(dep_data) 140 | dep_base = $$take_first(dep_data) 141 | dep_path = $$take_first(dep_data) 142 | dep_needs_cache = $$take_first(dep_data) 143 | 144 | $${dep_hash}.package = $$dep_pkg 145 | $${dep_hash}.version = $$dep_version 146 | $${dep_hash}.path = $${dep_base}$${dep_path} 147 | $${dep_hash}.exports = 148 | $${dep_hash}.local = 0 149 | $${dep_hash}.target = sub$${dep_hash} 150 | export($${dep_hash}.package) 151 | export($${dep_hash}.version) 152 | export($${dep_hash}.path) 153 | export($${dep_hash}.local) 154 | export($${dep_hash}.target) 155 | 156 | # Cache the package version for dependencies with undetermined versions 157 | !qdep_no_cache:equals(dep_needs_cache, True):!cache($${dep_hash}.version, set $$QDEP_CACHE_SCOPE):warning("Failed to cache package version for $$dep_pkg") 158 | 159 | # link the project and write the version if not already there 160 | exists("$$_PRO_FILE_PWD_/.qdep/$${dep_hash}/.version"): linked_version = $$cat("$$_PRO_FILE_PWD_/.qdep/$${dep_hash}/.version", lines) 161 | else: linked_version = 162 | qdep_ok = 163 | !equals($${dep_hash}.version, $$linked_version) { 164 | $${dep_hash}.file = $$system($$QDEP_TOOL prolink --link $$system_quote($${dep_base}) $$system_quote($$_PRO_FILE_PWD_) $${dep_hash} $$system_quote($${dep_path}), lines, qdep_ok) 165 | !equals(qdep_ok, 0):return(false) 166 | !write_file("$$_PRO_FILE_PWD_/.qdep/$${dep_hash}/.version", $${dep_hash}.version): \\ 167 | error("Failed to cache project dependency version for $$first($${dep_hash}.package)") 168 | } else { 169 | $${dep_hash}.file = $$system($$QDEP_TOOL prolink $$system_quote($$_PRO_FILE_PWD_) $${dep_hash} $$system_quote($${dep_path}), lines, qdep_ok) 170 | !equals(qdep_ok, 0):return(false) 171 | } 172 | export($${dep_hash}.file) 173 | 174 | # Find all further project dependencies that package depends on 175 | sub_deps = $$fromfile($$eval($${dep_hash}.path), QDEP_PROJECT_DEPENDS) 176 | !isEmpty(sub_deps) { 177 | qdep_ok = 178 | $${dep_hash}.depends = $$system($$QDEP_TOOL dephash --project $$sub_deps, lines, qdep_ok) 179 | !equals(qdep_ok, 0):return(false) 180 | export($${dep_hash}.depends) 181 | } 182 | SUBDIRS += $${dep_hash} 183 | export(SUBDIRS) 184 | __QDEP_INCLUDE_CACHE += $${dep_hash} 185 | export(__QDEP_INCLUDE_CACHE) 186 | 187 | # calls this method recursively for all dependencies 188 | !qdepCollectProjectDependencies($$sub_deps):return(false) 189 | } else: \\ 190 | !equals($${dep_hash}.package, $$dep_pkg): \\ 191 | warning("Detected includes of multiple different versions of the same dependency. Package \\"$$first($${dep_hash}.package)\\" is used, and package \\"$$dep_pkg\\" was detected.") 192 | } 193 | 194 | return(true) 195 | } 196 | 197 | # resolve qdep_depends of subdir dependencies to the actual hashes 198 | defineTest(qdepResolveSubdirDepends) { 199 | for(subproj, 1) { 200 | !isEmpty($${subproj}.qdep_depends) { 201 | qdep_dependencies = 202 | for(arg, $${subproj}.qdep_depends): qdep_dependencies += $$system_quote($$arg) 203 | qdep_ok = 204 | $${subproj}.depends += $$system($$QDEP_TOOL dephash --project $$qdep_dependencies, lines, qdep_ok) 205 | !equals(qdep_ok, 0):return(false) 206 | export($${subproj}.depends) 207 | } 208 | } 209 | return(true) 210 | } 211 | 212 | # pass the root dir and dependencies and create QDEP_LINK_DEPENDS values from it 213 | defineReplace(qdepResolveProjectLinkDeps) { 214 | qdep_dependencies = 215 | for(arg, 2): qdep_dependencies += $$system_quote($$arg) 216 | qdep_ok = 217 | dep_tuples = $$system($$QDEP_TOOL dephash --project --pkgpath $$qdep_dependencies, lines, qdep_ok) 218 | !equals(qdep_ok, 0):return() 219 | 220 | link_paths = 221 | for(qdep_tuple, dep_tuples) { 222 | tpl_args = $$split(qdep_tuple, ";") 223 | dep_hash = $$take_first(tpl_args) 224 | dep_path = $$join(tpl_args, ";") 225 | qdep_ok = 226 | link_paths += $$system($$QDEP_TOOL prolink $$system_quote($$absolute_path($$1, $$_PRO_FILE_PWD_)) $${dep_hash} $$system_quote($${dep_path}), lines, qdep_ok) 227 | !equals(qdep_ok, 0):return() 228 | } 229 | 230 | return($$link_paths) 231 | } 232 | 233 | # Write a quoted value for the given variable name as a single value 234 | defineReplace(qdepOutQuote) { 235 | result = 236 | var_name = $$1 237 | isEmpty(4): \\ 238 | intendent = $$escape_expand(\\t) 239 | else { 240 | intendent = 241 | for(x, "1..$$4"): \\ 242 | intendent = "$$intendent$$escape_expand(\\t)" 243 | } 244 | equals(3, prepend): \\ 245 | for(value, 2): result += "$$intendent$$var_name = $${LITERAL_DOLLAR}$${LITERAL_DOLLAR}quote($$value) $${LITERAL_DOLLAR}$${LITERAL_DOLLAR}$$var_name" 246 | else:equals(3, star): \\ 247 | for(value, 2): result += "$$intendent$$var_name *= $${LITERAL_DOLLAR}$${LITERAL_DOLLAR}quote($$value)" 248 | else:equals(3, pop): \\ 249 | for(value, 2): result += "$$intendent$${LITERAL_DOLLAR}$${LITERAL_DOLLAR}take_last($$var_name)" 250 | else: \\ 251 | for(value, 2): result += "$$intendent$$var_name += $${LITERAL_DOLLAR}$${LITERAL_DOLLAR}quote($$value)" 252 | return($$result) 253 | } 254 | 255 | # A function to create a pri file to include the library and all exported 256 | defineTest(qdepCreateExportPri) { 257 | # write include guard 258 | out_file_data = "!contains(__QDEP_EXPORT_QMAKE_INCLUDE_GUARD, $${LITERAL_DOLLAR}$${LITERAL_DOLLAR}PWD) {" 259 | out_file_data += $$qdepOutQuote(__QDEP_EXPORT_QMAKE_INCLUDE_GUARD, $${LITERAL_DOLLAR}$${LITERAL_DOLLAR}PWD) 260 | 261 | # write dependencies 262 | !static:!staticlib: \\ 263 | out_file_data += $$qdepOutQuote(__QDEP_IS_DYNAMIC_EXPORT_INCLUDE_STACK, true) 264 | for(link_dep, QDEP_LINK_DEPENDS): \\ 265 | out_file_data += "$$escape_expand(\\t)include($$qdepLinkExpand($$link_dep))" 266 | !static:!staticlib: \\ 267 | out_file_data += $$qdepOutQuote(__QDEP_IS_DYNAMIC_EXPORT_INCLUDE_STACK, true, pop) 268 | 269 | # write basic variables 270 | out_file_data += $$qdepOutQuote(DEFINES, $$QDEP_EXPORTED_DEFINES $$QDEP_DEFINES) 271 | out_file_data += $$qdepOutQuote(INCLUDEPATH, $$QDEP_EXPORTED_INCLUDEPATH $$QDEP_INCLUDEPATH) 272 | out_file_data += $$qdepOutQuote(LIBS, $$QDEP_EXPORTED_LIBS $$QDEP_LIBS) 273 | for(exp_var_key, QDEP_VAR_EXPORTS): out_file_data += $$qdepOutQuote($$exp_var_key, $$eval($$exp_var_key)) 274 | 275 | # write package cache 276 | for(dep_hash, __QDEP_INCLUDE_CACHE):equals($${dep_hash}.local, 1) { 277 | out_file_data += $$qdepOutQuote($${dep_hash}.package, $$eval($${dep_hash}.package)) 278 | out_file_data += $$qdepOutQuote($${dep_hash}.version, $$eval($${dep_hash}.version)) 279 | out_file_data += $$qdepOutQuote($${dep_hash}.path, $$eval($${dep_hash}.path)) 280 | out_file_data += $$qdepOutQuote($${dep_hash}.exports, $$eval($${dep_hash}.exports)) 281 | out_file_data += $$qdepOutQuote($${dep_hash}.local, 0) 282 | out_file_data += $$qdepOutQuote(__QDEP_INCLUDE_CACHE, $$dep_hash) 283 | } 284 | 285 | # write library linkage common parts 286 | isEmpty(DESTDIR) { 287 | out_libdir = $$OUT_PWD 288 | debug_and_release:CONFIG(release, debug|release): out_libdir = $${out_libdir}/release 289 | else:debug_and_release:CONFIG(debug, debug|release): out_libdir = $${out_libdir}/debug 290 | } else: out_libdir = $$absolute_path($$DESTDIR, $$OUT_PWD) 291 | 292 | !qdep_no_link { 293 | # write includepath 294 | out_file_data += $$qdepOutQuote(INCLUDEPATH, $$_PRO_FILE_PWD_) 295 | 296 | # write QT, PKGCONFIG and QDEP_LIBS (unless disabled) 297 | !qdep_no_export_link { 298 | qt: out_file_data += $$qdepOutQuote(QT, $$QT, star) 299 | link_pkgconfig: out_file_data += $$qdepOutQuote(PKGCONFIG, $$PKGCONFIG, star) 300 | } 301 | } 302 | 303 | # write static/dynamic specific library extra stuff 304 | static|staticlib:equals(TEMPLATE, lib) { 305 | out_file_data += "$$escape_expand(\\t)isEmpty(__QDEP_IS_DYNAMIC_EXPORT_INCLUDE_STACK) {" 306 | 307 | # write startup hooks 308 | debug_and_release:CONFIG(release, debug|release): out_file_data += "$$qdepOutQuote(__QDEP_HOOK_FILES, $$QDEP_GENERATED_DIR/release/qdep_$${TARGET}_hooks.h, "", 2)" 309 | else:debug_and_release:CONFIG(debug, debug|release): out_file_data += "$$qdepOutQuote(__QDEP_HOOK_FILES, $$QDEP_GENERATED_DIR/debug/qdep_$${TARGET}_hooks.h, "", 2)" 310 | else: out_file_data += "$$qdepOutQuote(__QDEP_HOOK_FILES, $$QDEP_GENERATED_DIR/qdep_$${TARGET}_hooks.h, "", 2)" 311 | 312 | # linkage 313 | !qdep_no_link { 314 | out_file_data += $$qdepOutQuote(DEPENDPATH, $$_PRO_FILE_PWD_, "", 2) 315 | 316 | win32-g++: out_file_data += $$qdepOutQuote(PRE_TARGETDEPS, "$${out_libdir}/lib$${TARGET}.a", "", 2) 317 | else:win32: out_file_data += $$qdepOutQuote(PRE_TARGETDEPS, "$${out_libdir}/$${TARGET}.lib", "", 2) 318 | else:unix: out_file_data += $$qdepOutQuote(PRE_TARGETDEPS, "$${out_libdir}/lib$${TARGET}.a", "", 2) 319 | 320 | out_file_data += $$qdepOutQuote(LIBS, "-l$${TARGET}", prepend, 2) 321 | out_file_data += $$qdepOutQuote(LIBS, "-L$${out_libdir}/", prepend, 2) 322 | } 323 | 324 | out_file_data += "$$escape_expand(\\t)}" 325 | } else { 326 | # linkage 327 | !qdep_no_link { 328 | equals(TEMPLATE, lib):out_file_data += $$qdepOutQuote(LIBS, "-l$${TARGET}", prepend) 329 | else { 330 | win32: bin_suffix = .exe 331 | out_file_data += $$qdepOutQuote(LIBS, "-l:$${TARGET}$${bin_suffix}", prepend) 332 | } 333 | out_file_data += $$qdepOutQuote(LIBS, "-L$${out_libdir}/", prepend) 334 | } 335 | } 336 | 337 | out_file_data += "}" 338 | write_file($$1, out_file_data):return(true) 339 | else:return(false) 340 | } 341 | 342 | # get the full pri path of a link dependency 343 | defineReplace(qdepLinkExpand) { 344 | base_path = $$1 345 | suffix = $$str_member($$base_path, -4, -1) 346 | 347 | !equals(suffix, ".pri") { 348 | equals(suffix, ".pro") { 349 | file_name = $$basename(base_path) 350 | file_name = "$$str_member($$file_name, 0, -5)_export.pri" 351 | base_path = $$dirname(base_path) 352 | } else { 353 | file_name = "$$basename(base_path)_export.pri" 354 | } 355 | debug_and_release:CONFIG(release, debug|release): base_path = $$base_path/release/$$file_name 356 | else:debug_and_release:CONFIG(debug, debug|release): base_path = $$base_path/debug/$$file_name 357 | else: base_path = $$base_path/$$file_name 358 | } 359 | 360 | base_path = $$absolute_path($$base_path, $$_PRO_FILE_PWD_) 361 | s_base_path = $$shadowed($$base_path) 362 | !isEmpty(s_base_path):exists($$s_base_path):return($$s_base_path) 363 | else:!isEmpty(base_path):exists($$base_path):return($$base_path) 364 | else:return() 365 | } 366 | 367 | # find the SUBDIRS project build directory that this qdep project was linked from 368 | defineReplace(qdepResolveLinkRoot) { 369 | path_segments = $$split(1, "/") 370 | path_count = $$size(path_segments) 371 | c_path = $$1 372 | check_next = 0 373 | for(index, 0..$$path_count) { 374 | equals(check_next, 1): \\ 375 | exists("$$c_path/Makefile"): \\ 376 | return($$c_path) 377 | 378 | c_name = $$basename(c_path) 379 | equals(c_name, ".qdep"): check_next = 1 380 | else: check_next = 0 381 | c_path = $$dirname(c_path) 382 | !exists($$c_path):return() 383 | } 384 | return() 385 | } 386 | 387 | # shell_escape a list of files 388 | defineReplace(qdepShellQuote) { 389 | out_files = 390 | for(input, ARGS): out_files += $$shell_quote($$absolute_path($$input, $$_PRO_FILE_PWD_)) 391 | return($$out_files) 392 | } 393 | 394 | # dump dependencies for update collection 395 | defineTest(qdepDumpUpdateDeps) { 396 | dump_data = $$_PRO_FILE_ 397 | dump_data += $$QDEP_DEPENDS 398 | dump_data += $$QDEP_PROJECT_SUBDIRS 399 | dump_data += $$QDEP_PROJECT_LINK_DEPENDS 400 | dump_data += $$QDEP_PROJECT_DEPENDS 401 | write_file($$OUT_PWD/qdep_depends.txt, dump_data):return(true) 402 | else:return(false) 403 | } 404 | 405 | # dump deps if in update mode 406 | __qdep_dump_dependencies: \\ 407 | !qdepDumpUpdateDeps(): \\ 408 | error("Failed to dump dependencies") 409 | 410 | # First transform project link depends to normal link depends 411 | !isEmpty(QDEP_PROJECT_ROOT): \\ 412 | !isEmpty(QDEP_PROJECT_LINK_DEPENDS): \\ 413 | QDEP_LINK_DEPENDS = $$qdepResolveProjectLinkDeps($$QDEP_PROJECT_ROOT, $$QDEP_PROJECT_LINK_DEPENDS) $$QDEP_LINK_DEPENDS # always prepend project link depends 414 | 415 | # Second transform project depends to normal link depends 416 | # This will only work if the project is imported as qdep project dependency 417 | !isEmpty(QDEP_PROJECT_DEPENDS) { 418 | __qdep_project_link_root = $$qdepResolveLinkRoot($$OUT_PWD) 419 | isEmpty(__qdep_project_link_root): warning("Failed to find including subdirs project - only use QDEP_PROJECT_DEPENDS in qdep project depenencies") 420 | else: QDEP_LINK_DEPENDS = $$qdepResolveProjectLinkDeps($$__qdep_project_link_root, $$QDEP_PROJECT_DEPENDS) $$QDEP_LINK_DEPENDS # always prepend project link depends 421 | } 422 | 423 | # Next collect all indirect dependencies 424 | # for GCC: create a link group, as these all add libs in arbitrary order 425 | !isEmpty(QDEP_LINK_DEPENDS): \\ 426 | for(link_dep, QDEP_LINK_DEPENDS): \\ 427 | !include($$qdepLinkExpand($$link_dep)): \\ 428 | error("Failed to include linked library $$link_dep") 429 | 430 | # Collect all dependencies and then include them 431 | !isEmpty(QDEP_DEPENDS): { 432 | __qdep_cached_hooks = $$QDEP_HOOK_FNS 433 | QDEP_HOOK_FNS = 434 | __qdep_cached_resources = $$RESOURCES 435 | RESOURCES = 436 | 437 | !qdepCollectDependencies($$QDEP_DEPENDS):error("Failed to collect all dependencies") 438 | else:for(dep, __QDEP_INCLUDE_CACHE):equals($${dep}.local, 1) { 439 | __qdep_define_offset = $$size(DEFINES) 440 | __qdep_include_offset = $$size(INCLUDEPATH) 441 | __qdep_libs_offset = $$size(LIBS) 442 | include($$first($${dep}.path)) { 443 | qdep_export_all|contains(QDEP_EXPORTS, $$first($${dep}.package)) { 444 | QDEP_EXPORTED_DEFINES += $$member(DEFINES, $$__qdep_define_offset, -1) 445 | export(QDEP_EXPORTED_DEFINES) 446 | QDEP_EXPORTED_INCLUDEPATH += $$member(INCLUDEPATH, $$__qdep_include_offset, -1) 447 | export(QDEP_EXPORTED_INCLUDEPATH) 448 | QDEP_EXPORTED_LIBS += $$member(LIBS, $$__qdep_libs_offset, -1) 449 | export(QDEP_EXPORTED_LIBS) 450 | } 451 | } else:error("Failed to include pri file $$first($${dep}.package)") 452 | } 453 | 454 | QDEP_HOOK_FNS += $$__qdep_cached_hooks 455 | RESOURCES += $$__qdep_cached_resources 456 | } 457 | 458 | # Collect all project dependencies 459 | equals(TEMPLATE, subdirs):!isEmpty(QDEP_PROJECT_SUBDIRS) { 460 | !qdepCollectProjectDependencies($$QDEP_PROJECT_SUBDIRS): \\ 461 | error("Failed to collect all project dependencies") 462 | !qdepResolveSubdirDepends($$SUBDIRS): \\ 463 | error("Failed to link all project dependencies") 464 | } 465 | 466 | # create special target for resource hooks in static libs 467 | # or if not, reference their hooks of static libs (as special compiler, only for non-static apps/libs) 468 | static|staticlib:equals(TEMPLATE, lib) { 469 | __qdep_hook_generator_c.name = qdep hookgen ${QMAKE_FILE_IN} 470 | __qdep_hook_generator_c.input = _PRO_FILE_ RESOURCES 471 | __qdep_hook_generator_c.variable_out = HEADERS 472 | __qdep_hook_generator_c.commands = $$QDEP_PATH hookgen --hooks $$QDEP_HOOK_FNS -- $${TARGET} ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN} 473 | __qdep_hook_generator_c.output = $$QDEP_GENERATED_SOURCES_DIR/qdep_$${TARGET}_hooks.h 474 | __qdep_hook_generator_c.CONFIG += target_predeps combine no_link 475 | __qdep_hook_generator_c.depends += $$QDEP_PATH 476 | QMAKE_EXTRA_COMPILERS += __qdep_hook_generator_c 477 | } else:!equals(TEMPLATE, aux) { 478 | __qdep_hook_importer_c.name = qdep hookimp ${QMAKE_FILE_IN} 479 | __qdep_hook_importer_c.input = _PRO_FILE_ __QDEP_HOOK_FILES 480 | __qdep_hook_importer_c.variable_out = GENERATED_SOURCES 481 | __qdep_hook_importer_c.commands = $$QDEP_PATH hookimp --hooks $$QDEP_HOOK_FNS -- ${QMAKE_FILE_OUT} ${QMAKE_FILE_IN} 482 | __qdep_hook_importer_c.output = $$QDEP_GENERATED_SOURCES_DIR/qdep_imported_hooks.cpp 483 | __qdep_hook_importer_c.CONFIG += target_predeps combine 484 | __qdep_hook_importer_c.depends += $$QDEP_PATH 485 | QMAKE_EXTRA_COMPILERS += __qdep_hook_importer_c 486 | } 487 | 488 | # Create lupdate target 489 | __qdep_lupdate_target.target = lupdate 490 | __qdep_lupdate_target.commands = $$QDEP_LUPDATE $$qdepShellQuote($$_PRO_FILE_PWD_ $$QDEP_LUPDATE_INPUTS) -ts $$qdepShellQuote($$TRANSLATIONS) 491 | __qdep_lupdate_target.depends += $$QDEP_LUPDATE_EXE $$_PRO_FILE_PWD_ $$QDEP_LUPDATE_INPUTS 492 | QMAKE_EXTRA_TARGETS += __qdep_lupdate_target 493 | 494 | # fix for broken lrelease make feature 495 | { 496 | isEmpty(LRELEASE_DIR): LRELEASE_DIR = .qm 497 | debug_and_release:CONFIG(release, debug|release): __qdep_lrelease_real_dir = $${LRELEASE_DIR}/release 498 | else:debug_and_release:CONFIG(debug, debug|release): __qdep_lrelease_real_dir = $${LRELEASE_DIR}/debug 499 | else: __qdep_lrelease_real_dir = $$LRELEASE_DIR 500 | __qdep_lrelease_real_dir = $$absolute_path($$__qdep_lrelease_real_dir, $$OUT_PWD) 501 | !exists($$__qdep_lrelease_real_dir): \\ 502 | !mkpath($$__qdep_lrelease_real_dir): \\ 503 | warning("Failed to create lrelease directory: $$__qdep_lrelease_real_dir") 504 | } 505 | qm_files.CONFIG += no_check_exist 506 | 507 | # Create special targets for translations 508 | !qdep_no_qm_combine { 509 | # move translations into temporary var for the compiler to work 510 | __QDEP_ORIGINAL_TRANSLATIONS = $$TRANSLATIONS 511 | TRANSLATIONS = 512 | 513 | # compiler for combined translations 514 | __qdep_qm_combine_c.name = qdep lrelease ${QMAKE_FILE_IN} 515 | __qdep_qm_combine_c.input = __QDEP_ORIGINAL_TRANSLATIONS 516 | __qdep_qm_combine_c.variable_out = TRANSLATIONS 517 | __qdep_qm_combine_c.commands = $$QDEP_PATH lconvert --combine $$qdepShellQuote($$QDEP_TRANSLATIONS) -- ${QMAKE_FILE_IN} ${QMAKE_FILE_OUT} $$QDEP_LCONVERT 518 | __qdep_qm_combine_c.output = $$QDEP_GENERATED_TS_DIR/${QMAKE_FILE_BASE}.ts 519 | __qdep_qm_combine_c.CONFIG += no_link 520 | __qdep_qm_combine_c.depends += $$QDEP_PATH $$QDEP_LCONVERT_EXE $$QDEP_TRANSLATIONS 521 | QMAKE_EXTRA_COMPILERS += __qdep_qm_combine_c 522 | 523 | # copy from lrelease.prf - needed as TRANSLATIONS is now empty 524 | for(translation, $$list($$__QDEP_ORIGINAL_TRANSLATIONS $$EXTRA_TRANSLATIONS)) { 525 | translation = $$basename(translation) 526 | QM_FILES += $$__qdep_lrelease_real_dir/$$replace(translation, \\\\..*$, .qm) 527 | } 528 | } else { 529 | # add qdep translations to extra to allow lrelease processing 530 | EXTRA_TRANSLATIONS += $$QDEP_TRANSLATIONS 531 | } 532 | 533 | # Create qdep pri export, if modules should be exported 534 | equals(TEMPLATE, lib):!qdep_no_link|qdep_link_export|qdep_export_all|!isEmpty(QDEP_EXPORTS): \\ 535 | !qdepCreateExportPri($$QDEP_EXPORT_PATH/$$QDEP_EXPORT_NAME): \\ 536 | error("Failed to create export file $$QDEP_EXPORT_FILE") 537 | 538 | DEFINES += $$QDEP_DEFINES 539 | INCLUDEPATH += $$QDEP_INCLUDEPATH 540 | LIBS += $$QDEP_LIBS 541 | """ 542 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qdep 2 | A very basic yet simple to use dependency management tool for qmake based projects. 3 | 4 | [![Travis Build Status](https://travis-ci.org/Skycoder42/qdep.svg?branch=master)](https://travis-ci.org/Skycoder42/qdep) 5 | [![Appveyor Build status](https://ci.appveyor.com/api/projects/status/s222vatjpd4ic70w/branch/master?svg=true)](https://ci.appveyor.com/project/Skycoder42/qdep/branch/master) 6 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/373a21f05f8847c29ce08739891631e8)](https://www.codacy.com/app/Skycoder42/qdep) 7 | [![AUR](https://img.shields.io/aur/version/qdep.svg)](https://aur.archlinux.org/packages/qdep/) 8 | [![PyPi](https://img.shields.io/pypi/v/qdep.svg)](https://pypi.org/project/qdep/) 9 | 10 | ## Table of contents 11 | - [qdep](#qdep) 12 | * [Features](#features) 13 | * [Design Goals/Scope](#design-goals-scope) 14 | * [Installation](#installation) 15 | + [Preparing qmake](#preparing-qmake) 16 | + [Shell completitions](#shell-completitions) 17 | * [Getting started](#getting-started) 18 | * [Getting deeper](#getting-deeper) 19 | + [Dependency IDs](#dependency-ids) 20 | - [Versioning](#versioning) 21 | - [Package-uniqueness and version conflicts](#package-uniqueness-and-version-conflicts) 22 | + [Normal dependencies](#normal-dependencies) 23 | - [Translations](#translations) 24 | - [Library support](#library-support) 25 | - [Creating normal dependencies](#creating-normal-dependencies) 26 | * [Creating qdep translations](#creating-qdep-translations) 27 | * [Resources and hooks](#resources-and-hooks) 28 | * [Automatic exports](#automatic-exports) 29 | + [Project dependencies](#project-dependencies) 30 | - [Creating project dependencies](#creating-project-dependencies) 31 | * [Documentation](#documentation) 32 | + [Command line interface](#command-line-interface) 33 | - [Public API operations:](#public-api-operations-) 34 | - [Private API operations:](#private-api-operations-) 35 | + [QMAKE-Feature](#qmake-feature) 36 | - [Variables](#variables) 37 | * [Common Variables](#common-variables) 38 | * [SUBDIRS extension](#subdirs-extension) 39 | * [Advanced Variables](#advanced-variables) 40 | - [Configuration values](#configuration-values) 41 | * [Input value](#input-value) 42 | * [Output values](#output-values) 43 | * [Defines](#defines) 44 | - [Environment variables](#environment-variables) 45 | - [Public make targets](#public-make-targets) 46 | - [Internal](#internal) 47 | * [Variables](#variables-1) 48 | * [Configuration values](#configuration-values-1) 49 | * [QMAKE test functions](#qmake-test-functions) 50 | * [QMAKE replace functions](#qmake-replace-functions) 51 | 52 | Table of contents generated with markdown-toc 53 | 54 | ## Features 55 | - Seamless integration in qmake projects - no extra files needed 56 | - Basic dependency management using git repositories as package sources 57 | - Globally caches source files to speed up builds 58 | - Packages are simple pri-files that are included by the target project 59 | - Recursive dependency solving 60 | - Allows branch and tag based versioning 61 | - Supports translations for qdep packages 62 | - Supports automatic export of qdep packages from dynamic libraries 63 | - Handles QRC-Resources and startup hooks to work even when used in static libraries 64 | - Supports special "Project dependencies" wich allows you to add whole qmake projects to a SUBDIRS project 65 | - Can generate "library export" pri files that provide an easy and reliable way to link against libraries 66 | - Implicitly supports exported qdep packages and projects 67 | 68 | ## Design Goals/Scope 69 | qdep was designed with the following goals in mind: 70 | 71 | 1. Full qmake integration: qdep should not require any extra commands to install packages and prepare a project to be built. Just running qmake on the target project should take care of this 72 | 2. Simple installation: Using python makes it easy to use the tool on any platform. Besides the install only a single setup command is needed to make qdep work for any Qt installation 73 | 3. Full Qt support: All features of Qt - including resources, startup hooks and translations should be supported with minimal effort for application developers 74 | 4. Library exports: Since all qdep packages are source based, having multiple libraries that use the same package can be problematic. With qdep, one can "export" a package from a library, making it available to all the others. 75 | 5. No additional server infrastructure: qdep should not require any extra servers to provide a package index. Any git repository can server as a package without any extra preparations needed 76 | 77 | Please note that qdep is kept intentionally small to fullfill exactly those goals. Other build systems or more complex package management features are not supported and never will be. This project will stay active until the Qt Company switches their build systems to CMake. At that point this project will either be dropped or ported to CMake, depending on what alternative solutions already exist at that point. 78 | 79 | ## Installation 80 | To install the package, follow one of the following possibilities. Please note that only Python >= 3.7 is officially supported. Earlier versions might work as well, but have not been tested. 81 | 82 | 1. **Arch Linux:** Use the AUR-Package: [qdep](https://aur.archlinux.org/packages/qdep/) 83 | 2. **Any Platform:** Install via pip: [`pip install qdep`](https://pypi.org/project/qdep/) 84 | 3. **Any Platform:** Clone the repository and install the sources directly: `python setup.py install` 85 | 86 | ### Preparing qmake 87 | After installing (except when using the AUR-package), you have to "enable" qdep for each Qt-Kit you want to use qdep with. This can be done by opening a terminal and calling: 88 | ```bash 89 | qdep prfgen --qmake "" 90 | ``` 91 | For example, if you have installed Qt 5.12.0 for MSVC 2017 x64 on windows in the default location (`C:\Qt`), the command would look like this: 92 | ```bash 93 | qdep prfgen --qmake "C:\Qt\5.12.0\msvc2017_64\bin\qmake.exe" 94 | ``` 95 | 96 | **Note:** Depending on how the corresponding Qt-Kit was installed, you might need to run the command with administrator/sudo permissions. Alternatively, you can call the command with `--dir /some/path` and export that very same path as value to the `QMAKEPATH` environment variable, if you have no such permissions. 97 | 98 | ### Shell completitions 99 | For Unix systems, qdep makes use of [argcomplete](https://argcomplete.readthedocs.io/en/latest/) to provide completitions for bash/zsh to activate them, add the following code your shell initializer scripts: 100 | 101 | For **zsh**, add this to `~/.zshrc`: 102 | ```bash 103 | autoload bashcompinit 104 | bashcompinit 105 | autoload compinit 106 | compinit 107 | eval "$(register-python-argcomplete qdep)" 108 | ``` 109 | 110 | For **bash**, add this to `~/.bashrc`: 111 | ``` 112 | eval "$(register-python-argcomplete qdep)" 113 | ``` 114 | 115 | When using BASH, you can alternatively use global completition - see [Activating global completion](https://argcomplete.readthedocs.io/en/latest/#activating-global-completion) for more details on that. Other shells might work as well, depending on how well argcomplete works with them. Refer to the argcomplete documentation and their GitHub Repository . 116 | 117 | ## Getting started 118 | The basic usage of qdep is very simple. For this example, we assume you want to add for example [QHotkey](https://github.com/Skycoder42/QHotkey) to a project via qdep. All you have to do is to install (and prepare) qdep and then add the following two lines to your pro-file: 119 | 120 | ```qmake 121 | QDEP_DEPENDS += Skycoder42/QHotkey 122 | !load(qdep):error("Failed to load qdep feature") 123 | ``` 124 | 125 | Thats it! The next time you run qmake qdep will automatically download the latest release and add them to your project. Just compile the project and you can use the library. A more explicit way to specify the package (and what that shorter string extends to) would be `https://github.com/Skycoder42/QHotkey.git@1.2.2/qhotkey.pri` 126 | 127 | ## Getting deeper 128 | Besides this basic functionality of referencing qdep packages, qdep offers a few additional things to make using (and developing) those easier. In the following sections, they will be explained in detail. 129 | 130 | ### Dependency IDs 131 | Qdep dependencies are described by IDs. These IDs follow the format `[@[/]]`. The only relevant part is the URL, and it can either be implicit or explicit. Implicit URLs follow the format `/` and are automatically expanded to `https://github.com//.git`. For the explicit format, all kinds of GIT-URLs are supportet, i.e. HTTPS, SSH and FILE urls. 132 | 133 | If you leave out the path part of a qdep package, qdep assumes that there is a pri file named `.pri` in the repositories root directory. If that is not the case, or if a package has multiple different pri files to choose from, you can specify a path relative to the repositories root to that pri file to use that one instead of the automatically detected one. 134 | 135 | #### Versioning 136 | Qdep supports 3 kinds of versioning: Unversioned, branches, tags. If you leave out the version, qdep will automatically query the corresponding repository and get the latest tag (by "creation") and use that one. If you do specify a version, it can either be a git tag or a git branch. In case of a tag, qdep will simply download it and assume it to be persistant, i.e. never check for updates on that specific tag. Referencing a branch however will lead to qdep "tracking" that branch, and before every build, qdep pulls on the branch to update if neccessary. 137 | 138 | Generelly speaking, it is recommended to use explicit tags. Implicit versioning is fine, too, but updates to packages might break your builds at times you do not want them to. Branch versioning is always dangerous and should only be used on explicitly stable branches or for package delevopment. 139 | 140 | #### Package-uniqueness and version conflicts 141 | When working with recursive package dependencies, it can sometimes happen that two different packages include different versions of the same package. qdep circumvents this issue by making shure only a single version of each package is ever included. The first version this is referenced is the one that is choosen. Explicit package paths however are not considered the same package, i.e. it is possible to include two different pri files from the same git repository. Generelly speaking, a packages unique identifier is determined by its `` and its `` - both case insensitive. 142 | 143 | ### Normal dependencies 144 | Normal dependencies, aka pri-dependencies specified via `QDEP_DEPENDS` are the primary dependency type of qdep. They are typically resolve to a simple pri files, that is included into your project by qdep. You can do anything in these pri files you would also in a "normal" pri file. However, there are a few extra things that become possible when including qdep dependencies. 145 | 146 | #### Translations 147 | The first feature is extended support for translations. Qdep packages can come with translation source files for their own sources. These are typically exported via the `QDEP_TRANSLATIONS` qmake variable. When creating your own translations, qdep will automatically merge these with your own translations at build time. This however onyl works if you make use of the `lrelease` qmake feature, provided by qt. See [QMake TRANSLATIONS](https://doc.qt.io/qt-5/qmake-variable-reference.html#translations) and [QMake QM_FILES_INSTALL_PAT](https://doc.qt.io/qt-5/qmake-variable-reference.html#qm-files-install-path) for more details. 148 | 149 | #### Library support 150 | When using qdep in static or dynamic libraries, there are often special steps needed to make that fully work. However, qdep takes care of those steps and performs them for you. The only thing you need to do is enable library exports from your library and then import that export from your primary project. For example, assuimg you have the following project structure: 151 | ``` 152 | root (subdirs) 153 | |-library (lib) 154 | |-app (app) 155 | ``` 156 | And you have a library that depends on QHotkey. If you want to use this library from app, you would create the library pro file as follows: 157 | ```qmake 158 | TEMPLATE = lib 159 | CONFIG += static # dynamic libraries are also possible, but dependencies must support exporting for them to work on windows 160 | 161 | # ... 162 | 163 | QDEP_DEPENDS += Skycoder42/QHotkey 164 | QDEP_EXPORTS += Skycoder42/QHotkey 165 | CONFIG += qdep_link_export 166 | !load(qdep):error("Failed to load qdep feature") 167 | ``` 168 | And then reference the library in the app pro file. This also takes care of linking the library to the app, so no additional `INCLUDEPATH` or `LIBS` changes are needed: 169 | ```qmake 170 | TEMPLATE = app 171 | 172 | # ... 173 | 174 | QDEP_LINK_DEPENDS += ../library 175 | !load(qdep):error("Failed to load qdep feature") 176 | ``` 177 | And thats it! You can now use the QHotkey in the library and the app project without any additional work, as qdep will reference the QHotkey that is now embedded into the library project. 178 | 179 | **Note:** This will also work for dynamic librabries, but only if the *explicitly* support qdep package exporting. If not, linking will fail at least on windows, and possibly on other platforms as well. 180 | 181 | #### Creating normal dependencies 182 | This section is intended for developers that want to create their own qdep packages. Generally speaking, there is not much you need to do different from creating normal pri includes. However, there are a few small things in qdep you can use to your advantage to create better packages. They are described in the following sub sections and are: 183 | 184 | - Translation generation 185 | - Resources and startup hooks 186 | - automatic exports 187 | 188 | ##### Creating qdep translations 189 | The only difference when creating translations with qdep is where you put them. Instead of TRANSLATIONS, create a qmake variable called QDEP_TRANSLATIONS and add all the ts files you want to generate to it. Next, call the following command to actually generate the ts-files based on your pri file: 190 | ```bash 191 | qdep lupdate --qmake "" --pri-file "" [-- ...] 192 | ``` 193 | And thats it. You should now be able to find the generated TS-files where you wanted them to be as specified in the pri file. 194 | 195 | When creating packages that should work with and without qdep, you can add the following to your pri file to still make these translations available if the package is included without qdep: 196 | ```qmake 197 | !qdep_build: EXTRA_TRANSLATIONS += $$QDEP_TRANSLATIONS 198 | ``` 199 | 200 | ##### Resources and hooks 201 | One thing that can be problematic, especially when working with static libraries, is the use of RESOURCES and startup hooks. To make it work, qdep automatically generates code to load them. For resources, there is nothing special you need to do as package developer. 202 | 203 | For hooks however, thats a different story. Assuming you have the following function you want to be run as `Q_COREAPP_STARTUP_FUNCTION`: 204 | ```cpp 205 | void my_package_startup_hook() 206 | { 207 | doStuff(); 208 | } 209 | ``` 210 | You will have to add the following line to your qdep pri file to make shure this hook actually gets called: 211 | ```pro 212 | QDEP_HOOK_FNS += my_package_startup_hook 213 | ``` 214 | And with that, qdep will automatically generate the code needed to call this method as `Q_COREAPP_STARTUP_FUNCTION`. 215 | 216 | When creating packages that should work with and without qdep, you can add the following to the cpp file that contains the hook function to make it work for non-static projects, even if the package is included without qdep: 217 | ```cpp 218 | #ifndef QDEP_BUILD 219 | #include 220 | Q_COREAPP_STARTUP_FUNCTION(my_package_startup_hook) 221 | #endif 222 | ``` 223 | 224 | ##### Automatic exports 225 | Another very useful tool are automatic package exports. This allows qdep to automatically export a qdep package from a dynamic library, so other applications that link to that library can use the exported qdep packages API. This is basically equivalent to the following: 226 | 227 | ```cpp 228 | #ifdef BUILD_PACKAGE_AS_LIBRARY 229 | #ifdef IS_DLL_BUILD 230 | #define MY_PACKAGE_EXPORT Q_DECL_EXPORT 231 | #else 232 | #define MY_PACKAGE_EXPORT Q_DECL_IMPORT 233 | #endif 234 | #else 235 | #define MY_PACKAGE_EXPORT 236 | #endif 237 | 238 | class MY_PACKAGE_EXPORT MyClass { 239 | // ... 240 | }; 241 | ``` 242 | 243 | qdep basically automates the define part, so you don't have to care about correctly defining all those macros and your code can be reduced to: 244 | ```cpp 245 | class MY_PACKAGE_EXPORT MyClass { 246 | // ... 247 | }; 248 | ``` 249 | To make it work, simply add the following to your pri file: 250 | ```qmake 251 | QDEP_PACKAGE_EXPORTS += MY_PACKAGE_EXPORT 252 | ``` 253 | And thats it! When using the package normally, qdep will automatically add an empty define that defines MY_PACKAGE_EXPORT to nothing. When building a dynamic library and the end user wants to export the package, it gets defined as Q_DECL_EXPORT (and Q_DECL_IMPORT for consuming applications). 254 | 255 | When creating packages that should work with and without qdep, you can add the following to the pri file to manually define the macro to nothing, even if the package is included without qdep: 256 | ```qmake 257 | !qdep_build: DEFINES += "MY_PACKAGE_EXPORT=" 258 | ``` 259 | 260 | ### Project dependencies 261 | Another side of qdep besides normal pri dependencies are full project dependencies. In this scenario, you include a full qmake project into your tree as child of a SUBDIRS projects. This allows the use of qdep package export pri files to link to these projects without much effort. To make use of this feature, you have to use a subdirs project that references the pro dependency, as well as normal projects that link against it. One great advantage of using project dependencies is, that they can reference other project dependencies they depend on, which means even for full projects, qdep takes care of the recursive dependency resolving for you. 262 | 263 | To get started, assume the following project structure: 264 | ``` 265 | root (subdirs) 266 | |--libs (subdirs) 267 | | |--lib (lib) 268 | |--app (app) 269 | ``` 270 | Both lib and app are assumed to depend on a theoretical qdep project dependency name `Skycoder42/SuperLib`, but app also depends on lib. 271 | 272 | The first step would be to choose a subdirs project to add the dependency to. For this example, the libs project is choosen. Add the following lines to add the dependency: 273 | ```qmake 274 | TEMPLATE = subdirs 275 | SUBDIRS += lib 276 | 277 | # .... 278 | 279 | QDEP_PROJECT_SUBDIRS += Skycoder42/SuperLib 280 | lib.qdep_depends += Skycoder42/SuperLib 281 | !load(qdep):error("Failed to load qdep feature") 282 | ``` 283 | The `QDEP_PROJECT_SUBDIRS` is used to actually pull in the project dependency, while adding it to `lib.qdep_depends` *only* makes sure that the qdep dependency is built before lib. This is not needed if lib does not depend on the qdep dependency. It is however recommended to always have a seperate subdirs project for qdep dependencies, i.e. for this concrete example it would be better to move the lib project one stage up or create another subdir project within libs that references the qdep dependencies. 284 | 285 | Next, we need to reference the library itself in app/lib. The procedure is the same for both, so here it is only shown for the app project as an example. In the app pro file, add the lines: 286 | ```qmake 287 | QDEP_PROJECT_ROOT = libs # Or "./libs" - would be ".." for the lib project 288 | QDEP_PROJECT_LINK_DEPENDS += Skycoder42/SuperLib 289 | !load(qdep):error("Failed to load qdep feature") 290 | ``` 291 | `QDEP_PROJECT_ROOT` tells qdep where the project is located, that contains the reference to the actual qdep project dependency, and `QDEP_PROJECT_LINK_DEPENDS` list all the dependencies this project (app) depends on. If any dependency listed there was not specified in the libs project via `QDEP_PROJECT_SUBDIRS`, the build will fail. 292 | 293 | And with that, the project dependency has been sucessfully added and referenced. With the next build, the project would be downloaded, compiled and linked against app/lib. 294 | 295 | #### Creating project dependencies 296 | Generally speaking, project dependencies are just normal qmake projects. However, such a project should **always** include the qdep feature and add `qdep_link_export` to the config, as without the generated export pri file, it will not be usable as qdep project dependency. But besides that, you can do anything you want, i.e. add other normal qdep pri dependencies etc. and even export them if needed. 297 | 298 | However, there is one additional feature that is only possible with qdep project dependencies: You can directly reference other qdep project dependencies. Doing so will make sure that whichever subdirs project that includes this project will also include the dependencies as subdirs and ensure the qmake build order, as well as referencing the corresponding export pri files. To for example reference `Skycoder42/SuperLib` from within a qdep project dependency, add the following: 299 | ```qmake 300 | QDEP_PROJECT_DEPENDS += Skycoder42/SuperLib 301 | !load(qdep):error("Failed to load qdep feature") 302 | ``` 303 | 304 | ## Documentation 305 | In the following sections, all the functions, variables etc. of qdep are documented for reference. 306 | 307 | ### Command line interface 308 | qdep has a public and a private command line API. The public API is intended to be used by delevopers, while the internal API is used by the qdep qmake feature to perform various operations. The following sections list all the commands with a short description. For more details on each command, type `qdep --help` 309 | 310 | #### Public API operations: 311 | ``` 312 | prfgen Generate a qmake project feature (prf) for the given qmake. 313 | init Initialize a pro file to use qdep by adding the required lines. 314 | lupdate Run lupdate for the QDEP_TRANSLATION variable in a given pri 315 | file. 316 | clear Remove all sources from the users global cache. 317 | versions List all known versions/tags of the given package 318 | query Query details about a given package identifier 319 | get Download the sources of one ore more packages into the source 320 | cache. 321 | update Check for newer versions of used packages and optionally update 322 | them. 323 | ``` 324 | 325 | #### Private API operations: 326 | ``` 327 | dephash Generated unique identifying hashes for qdep 328 | packages. 329 | pkgresolve Download the given qdep package and extract relevant 330 | information from it. 331 | hookgen Generate a header file with a method to load all 332 | resource hooks. 333 | hookimp Generate a source file that includes and runs all 334 | qdep hooks as normal startup hook. 335 | lconvert Combine ts files with translations from qdep 336 | packages. 337 | prolink Resolve the path a linked project dependency would 338 | be at. 339 | ``` 340 | 341 | ### QMAKE-Feature 342 | This is the documentation of the qmake feature that is generated by qdep and loaded by adding `load(qdep)` to your project. All variables, CONFIG-flags and more are documented below. 343 | 344 | #### Variables 345 | ##### Common Variables 346 | Name | Direction | Default | Descriptions 347 | ----------------------------|-----------|-------------------------------|-------------- 348 | QDEP_PATH | in/out | `` | Holds the path to the qdep binary to be used. Can be overwritten to specify a custom locations 349 | QDEP_VERSION | out | `` | The version of qdep which was used to generate the qdep feature 350 | QDEP_GENERATED_DIR | in | `$$OUT_PWD` | The sub-directory in the build folder where qdep should place all it's generated files. Can be an absolute or relative path 351 | QDEP_EXPORT_PATH | in | `$$QDEP_GENERATED_DIR/` | The directory where to place export pri files for libraries that export dependencies. Can be relative to OUT_PWD or absolute. Use QDEP_EXPORT_NAME to get the name of that file without a path 352 | QDEP_DEPENDS | in | `` | Specify all dependencies to qdep packages to be included into your project 353 | QDEP_LINK_DEPENDS | in | `` | Reference other projects in the same build tree that this project should link against. Those projects must be exporting a pri file 354 | QDEP_PROJECT_SUBDIRS | in | `` | Specify all dependencies to qdep projects to be added as qmake project to the SUBDIRS variable. Only evaluted in projects that specify TEMPLATE as `subdirs` 355 | QDEP_PROJECT_ROOT | in | `` | The path to a project qmake project directory or file to resolve QDEP_PROJECT_LINK_DEPENDS against 356 | QDEP_PROJECT_LINK_DEPENDS | in | `` | Sepcify all dependencies to qdep projects this project should be linked against. The dependencies are assumed to be provided in that project via QDEP_PROJECT_SUBDIRS. 357 | QDEP_DEFINES | in | `` | Specify DEFINES that are exported if the library exports a pri file. All values are automatically added to DEFINES by qdep 358 | QDEP_INCLUDEPATH | in | `` | Specify INCLUDEPATHS that are exported if the library exports a pri file. All values are automatically added to INCLUDEPATH by qdep 359 | QDEP_LIBS | in | `` | Specify LIBS that are exported if the library exports a pri file. All values are automatically added to LIBS by qdep 360 | QDEP_EXPORTS | in | `` | Specify qdep dependencies of which the API should be exported. Can be useful to export packages when used in dynamic libraries - only works if packages explicitly support this 361 | QDEP_LUPDATE_INPUTS | in | `` | Additional files or folders to parse for translations when running `make lupdate` 362 | QDEP_PACKAGE_EXPORTS | in (pkg) | `` | Variables to be defined as import/export/nothing and be used as prefix to a class. 363 | QDEP_TRANSLATIONS | in (pkg) | `` | Specify translation subfiles within a qdep dependency to be merged with the translations of the project they are included from 364 | QDEP_PROJECT_DEPENDS | in (pkg) | `` | Specify all dependencies to qdep projects this qdep project depends on. Can only be used in qdep project dependencies and adds it's contents to QDEP_PROJECT_SUBDIRS of the project that includes this dependency 365 | QDEP_VAR_EXPORTS | in (pkg) | `` | Specify the names of additional qmake variables that should be exported from a qdep dependency besides DEFINES and INCLUDEPATH 366 | 367 | ##### SUBDIRS extension 368 | - `qdep_depends`: Can be added to any variable passed to `SUBDIRS` in a project that uses `QDEP_PROJECT_SUBDIRS` to specify that a certain subdir project depends on that specific package. This does **not** take care of linkage etc. It only ensures that the make targets are build in the correct order. 369 | 370 | ##### Advanced Variables 371 | Name | Direction | Default | Descriptions 372 | ----------------------------|-----------|------------------------------------------|-------------- 373 | QDEP_TOOL | in/out | `` | The escaped command line base for qdep commands run from within qmake 374 | QDEP_CACHE_SCOPE | in | `stash` | The method of caching to be used to cache various qmake related stuff. Can be <empty>, super or stash 375 | QDEP_GENERATED_SOURCES_DIR | out | `$$QDEP_GENERATED_DIR/` | The directory where generated source files are placed. Is determined by the build configuration to take debug/release builds into account 376 | QDEP_GENERATED_TS_DIR | in | `$$QDEP_GENERATED_DIR/.qdepts/` | The directory where generated translation sources are placed. 377 | QDEP_LUPDATE | in | `lupdate -recursive -locations relative` | The path to the lupdate tool and additional arguments for lupdate in the `make lupdate` command to control it's behaviour 378 | QDEP_LCONVERT | in | `lconvert -sort-contexts` | The path to the lconvert tool and additional arguments to be used to combine translations 379 | QDEP_EXPORT_NAME | in/out | `_export.pri` | The name of a generated library import file. Must be only the name of the file, use QDEP_EXPORT_PATH to specify the location 380 | QDEP_EXPORTED_DEFINES | out | `` | DEFINES that come from QDEP_PACKAGE_EXPORTS or DEFINES from any directly included qdep dependency 381 | QDEP_EXPORTED_INCLUDEPATH | out | `` | INCLUDEPATHs that come from any directly included qdep dependency 382 | QDEP_EXPORTED_LIBS | out | `` | LIBS that come from any directly included qdep dependency 383 | QDEP_HOOK_FNS | out | `` | Holds all names of functions to be run as startup hook by qdep 384 | 385 | #### Configuration values 386 | ##### Input value 387 | - `qdep_force`: Enforce the full evaluation of qdep, even if the pro file is beeing evaluated without a context (This is potentially dangerous and should not be used unless absolutely neccessary) 388 | - `qdep_no_pull`: Do not pull for newer versions of branch-based dependencies. Cloning new packages will still work 389 | - `qdep_no_clone`: Do not clone completely new packages or package versions. Pulling on already cached branches will still work 390 | - `qdep_no_cache`: Do not cache the used versions of packages without a specified versions. Instead the latest version is queried on every run 391 | - `qdep_export_all`: export all dependant packages, i.e. any QDEP_PACKAGE_EXPORTS for every package are treated as if added to QDEP_EXPORTS 392 | - `qdep_no_link`: When exporting packages from a library, do not add the qmake code to link the library (and to it's includes) to the generate export.pri file 393 | - `qdep_no_qm_combine`: Do not combine TRANSLATIONS with QDEP_TRANSLATIONS. Instead treat QDEP_TRANSLATIONS as EXTRA_TRANSLATIONS and generate seperate qm files for them 394 | - `qdep_link_export`: Enforce the creation of an export pri file. Normally only libraries without qdep_no_link defined or projects that specify qdep_export_all or have values in QDEP_EXPORTS generate such files 395 | - `qdep_no_export_link`: Do not export contents of QT, PKGCONFIG or QDEP_LIBS in a generated export pri file 396 | 397 | ##### Output values 398 | - `qdep_build`: Is set in case the qdep features was loaded correctly and is enabled 399 | 400 | ##### Defines 401 | - `QDEP_BUILD`: Is defined in case the qdep features was loaded correctly and is enabled 402 | 403 | #### Environment variables 404 | - `QDEP_CACHE_DIR`: The directory where to cache downloaded sources. Is automatically determined for every system but can be overwritten with this variable 405 | - `QDEP_SOURCE_OVERRIDE`: Allows to provide a mapping in the format `;^;`. This will make qdep automatically replace any occurance of `pkg1` with `pkg2` etc. Can be used by developers to temporarily overwrite packages 406 | - `QDEP_DEFAULT_PKG_FN`: A template that is used to resolve non-url packages like `User/package` to a full url. The default method for that is `https://github.com/{}.git` - with `{}` being replaced by the short package name. 407 | 408 | #### Public make targets 409 | - `make lupdate`: Runs the `lupdate` tool to update your translations in a quick and easy manner, without having to rely on potentially buggy pro-file-parsing. 410 | 411 | #### Internal 412 | ##### Variables 413 | - `__QDEP_PRIVATE_SEPERATOR`: Seperator between datasets 414 | - `__QDEP_TUPLE_SEPERATOR`: Seperator between values within a dataset 415 | - `__QDEP_INCLUDE_CACHE`: objects with details about included depdendencies of all kinds 416 | - `__QDEP_ORIGINAL_TRANSLATIONS`: All original translation that were in TRANSLATIONS before apply translation combination with QDEP_TRANSLATIONS from included packages 417 | - `__QDEP_HOOK_FILES`: Paths to header files that contain hook definitions that must be included and loaded by a project 418 | - `__QDEP_EXPORT_QMAKE_INCLUDE_GUARD`: a cache of included exported pri files to ensure each gets included only once 419 | 420 | ##### Configuration values 421 | - `__qdep_script_version`: The version detected at runtime as reported by the qdep executable 422 | - `__qdep_dump_dependencies`: Create a file named qdep_depends.txt in the build directory that contains all direct dependencies of the project 423 | 424 | ##### QMAKE test functions 425 | - `qdepCollectDependencies(dependencies ...)`: Downloads all specified normal dependencies and adds them to `__QDEP_INCLUDE_CACHE` to be linked later by qdep. Works recursively 426 | - `qdepCollectProjectDependencies(dependencies ...)`: Downloads all specified project dependencies and adds them to `SUBDIRS`. Works recursively 427 | - `qdepResolveSubdirDepends(subdir-vars ...)`: Converts the values of the `qdep_depends` subvar of all variables passed to the function to normal subdirs `depends` subvars 428 | - `qdepCreateExportPri(path)`: Creates a file named path and adds all export-related code to it 429 | - `qdepShellQuote(paths ...)`: Makes the given paths absolute based on `_PRO_FILE_PWD_` and escapes them using `shell_quote` 430 | - `qdepDumpUpdateDeps()`: Creates a file named qdep_depends.txt in the build directory that contains all direct dependencies of the project 431 | 432 | ##### QMAKE replace functions 433 | - `qdepResolveProjectLinkDeps(project-root, link-depends ...)`: Resolves all link-depends project packages to subdir paths, assuming they are provided by the project located at project-root 434 | - `qdepOutQuote(name, values)`: Creates a number of lines of qmake code that assing each value in values to a variable named name, in a securely quoted manner 435 | - `qdepLinkExpand(path)`: Expands a shortened or relative path to a project that exports dependencies to get the path of the export pri file 436 | - `qdepResolveLinkRoot(path)`: Searches for a top-level qmake project, that has this project included as project dependency and returns the path to that pro file. Search is started based on path, which must be a build directory 437 | --------------------------------------------------------------------------------