diff options
author | Alexandru Croitor <alexandru.croitor@qt.io> | 2017-11-07 16:14:11 +0100 |
---|---|---|
committer | Alexandru Croitor <alexandru.croitor@qt.io> | 2017-11-15 15:47:09 +0000 |
commit | e455d995be989cbdfef2bcd54fd7057a9b036b52 (patch) | |
tree | 38a2865cb44350696d60fd15cd5b46a9278d17d9 | |
parent | 49fb9494ba6589cacda3c9e76fc354650a9fd87e (diff) |
Make standalone installations relocatable
This is achieved by registering a qt.conf file with a Prefix pointing
to a directory relative to the loaded PySide2 module (e.g. QtCore).
Thus Qt does not crash due to not finding platform plugins.
Because this change would affect tests, which are ran before the
PySide package is installed, a new environment variable called
PYSIDE_DISABLE_INTERNAL_QT_CONF is introduced. This variable disables
the registration of the internal qt.conf file, thus it will not point
to a not yet created location, which will allow tests to run as
before.
Change-Id: I5a96037adfafe1f08ea57535aa4a2a0d1660dfaf
Task-number: PYSIDE-558
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Reviewed-by: Christian Tismer <tismer@stackless.com>
-rw-r--r-- | setup.py | 21 | ||||
-rw-r--r-- | sources/pyside2/PySide2/QtCore/typesystem_core_common.xml | 8 | ||||
-rw-r--r-- | sources/pyside2/libpyside/CMakeLists.txt | 7 | ||||
-rw-r--r-- | sources/pyside2/libpyside/pyside.cpp | 130 | ||||
-rw-r--r-- | sources/pyside2/libpyside/pyside.h | 13 | ||||
-rw-r--r-- | sources/pyside2/tests/CMakeLists.txt | 2 |
6 files changed, 176 insertions, 5 deletions
@@ -61,6 +61,15 @@ without rebuilding entire PySide2 every time: # Then we create bdist_egg reusing PySide2 build with option --only-package python setup.py bdist_egg --only-package --qmake=c:\Qt\4.8.5\bin\qmake.exe --cmake=c:\tools\cmake\bin\cmake.exe --opnessl=c:\libs\OpenSSL32bit\bin +You can use the option --qt-conf-prefix to pass a path relative to the PySide2 installed package, +which will be embedded into an auto-generated qt.conf registered in the Qt resource system. This +path will serve as the PrefixPath for QLibraryInfo, thus allowing to choose where Qt plugins +should be loaded from. This option overrides the usual prefix chosen by --standalone option, or when +building on Windows. +To temporarily disable registration of the internal qt.conf file, a new environment variable called +PYSIDE_DISABLE_INTERNAL_QT_CONF is introduced. You should assign the integer "1" to disable the +internal qt.conf, or "0" (or leave empty) to keep use the internal qt.conf file. + For development purposes the following options might be of use, when using "setup.py build": --reuse-build option allows recompiling only the modified sources and not the whole world, shortening development iteration time, @@ -241,6 +250,7 @@ OPTION_SKIP_CMAKE = has_option("skip-cmake") OPTION_SKIP_MAKE_INSTALL = has_option("skip-make-install") OPTION_SKIP_PACKAGING = has_option("skip-packaging") OPTION_RPATH_VALUES = option_value("rpath") +OPTION_QT_CONF_PREFIX = option_value("qt-conf-prefix") if OPTION_QT_VERSION is None: OPTION_QT_VERSION = "5" @@ -854,6 +864,17 @@ class pyside_build(_build): if self.build_type.lower() == 'debug': cmake_cmd.append("-DPYTHON_DEBUG_LIBRARY=%s" % self.py_library) + if extension.lower() == "pyside2": + pyside_qt_conf_prefix = '' + if OPTION_QT_CONF_PREFIX: + pyside_qt_conf_prefix = OPTION_QT_CONF_PREFIX + else: + if OPTION_STANDALONE: + pyside_qt_conf_prefix = '"Qt"' + if sys.platform == 'win32': + pyside_qt_conf_prefix = '"."' + cmake_cmd.append("-DPYSIDE_QT_CONF_PREFIX=%s" % pyside_qt_conf_prefix) + if extension.lower() == "shiboken2": cmake_cmd.append("-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=yes") if sys.version_info[0] > 2: diff --git a/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml b/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml index 951d943ee..65f68882d 100644 --- a/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml +++ b/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml @@ -74,6 +74,10 @@ <function signature="qrand()" /> <function signature="qsrand(uint)" /> + <inject-code class="native" position="beginning"> + #include <pyside.h> + </inject-code> + <template name="use_stream_for_format_security"> // Uses the stream version for security reasons // see gcc man page at -Wformat-security @@ -1039,12 +1043,10 @@ Shiboken::Conversions::registerConverterName(SbkPySide2_QtCoreTypeConverters[SBK_QSTRING_IDX], "str"); Shiboken::Conversions::registerConverterName(SbkPySide2_QtCoreTypeConverters[SBK_QTCORE_QLIST_QVARIANT_IDX], "QVariantList"); + PySide::registerInternalQtConf(); PySide::init(module); Py_AtExit(QtCoreModuleExit); </inject-code> - <inject-code class="native" position="beginning"> - #include <pyside.h> - </inject-code> <inject-code class="native" position="beginning"> // Define a global variable to handle qInstallMessageHandler callback diff --git a/sources/pyside2/libpyside/CMakeLists.txt b/sources/pyside2/libpyside/CMakeLists.txt index 1cddebc07..de29269a4 100644 --- a/sources/pyside2/libpyside/CMakeLists.txt +++ b/sources/pyside2/libpyside/CMakeLists.txt @@ -111,6 +111,13 @@ if(QML_SUPPORT) target_compile_definitions(pyside2 PUBLIC PYSIDE_QML_SUPPORT=1) endif() +if(PYSIDE_QT_CONF_PREFIX) + set_property(SOURCE pyside.cpp + APPEND + PROPERTY COMPILE_DEFINITIONS + PYSIDE_QT_CONF_PREFIX=${PYSIDE_QT_CONF_PREFIX}) +endif() + # # install stuff # diff --git a/sources/pyside2/libpyside/pyside.cpp b/sources/pyside2/libpyside/pyside.cpp index 17366ce6e..b223edc6c 100644 --- a/sources/pyside2/libpyside/pyside.cpp +++ b/sources/pyside2/libpyside/pyside.cpp @@ -61,14 +61,20 @@ #include <typeinfo> #include <cstring> #include <cctype> -#include <QStack> +#include <QByteArray> #include <QCoreApplication> #include <QDebug> +#include <QDir> +#include <QFileInfo> #include <QSharedPointer> +#include <QStack> static QStack<PySide::CleanupFunction> cleanupFunctionList; static void* qobjectNextAddr; +extern bool qRegisterResourceData(int, const unsigned char *, const unsigned char *, + const unsigned char *); + namespace PySide { @@ -387,5 +393,127 @@ void setQuickRegisterItemFunction(QuickRegisterItemFunction function) } #endif // PYSIDE_QML_SUPPORT +// Inspired by Shiboken::String::toCString; +QString pyStringToQString(PyObject *str) { + if (str == Py_None) + return QString(); + +#ifdef IS_PY3K + if (PyUnicode_Check(str)) { + const char *unicodeBuffer = _PyUnicode_AsString(str); + if (unicodeBuffer) + return QString::fromUtf8(unicodeBuffer); + } +#endif + if (PyBytes_Check(str)) { + const char *asciiBuffer = PyBytes_AS_STRING(str); + if (asciiBuffer) + return QString::fromLatin1(asciiBuffer); + } + return QString(); +} + +static const unsigned char qt_resource_name[] = { + // qt + 0x0,0x2, + 0x0,0x0,0x7,0x84, + 0x0,0x71, + 0x0,0x74, + // etc + 0x0,0x3, + 0x0,0x0,0x6c,0xa3, + 0x0,0x65, + 0x0,0x74,0x0,0x63, + // qt.conf + 0x0,0x7, + 0x8,0x74,0xa6,0xa6, + 0x0,0x71, + 0x0,0x74,0x0,0x2e,0x0,0x63,0x0,0x6f,0x0,0x6e,0x0,0x66 +}; + +static const unsigned char qt_resource_struct[] = { + // : + 0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x1, + // :/qt + 0x0,0x0,0x0,0x0,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x2, + // :/qt/etc + 0x0,0x0,0x0,0xa,0x0,0x2,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x3, + // :/qt/etc/qt.conf + 0x0,0x0,0x0,0x16,0x0,0x0,0x0,0x0,0x0,0x1,0x0,0x0,0x0,0x0 +}; + +bool registerInternalQtConf() +{ + // Guard to ensure single registration. +#ifdef PYSIDE_QT_CONF_PREFIX + static bool registrationAttempted = false; +#else + static bool registrationAttempted = true; +#endif + static bool isRegistered = false; + if (registrationAttempted) + return isRegistered; + registrationAttempted = true; + + // Allow disabling the usage of the internal qt.conf. This is necessary for tests to work, + // because tests are executed before the package is installed, and thus the Prefix specified + // in qt.conf would point to a not yet existing location. + bool disableInternalQtConf = + qEnvironmentVariableIntValue("PYSIDE_DISABLE_INTERNAL_QT_CONF") > 0 ? true : false; + if (disableInternalQtConf) { + registrationAttempted = true; + return false; + } + + PyObject *pysideModule = PyImport_ImportModule("PySide2"); + if (!pysideModule) + return false; + + // Querying __file__ should be done only for modules that have finished their initialization. + // Thus querying for the top-level PySide2 package works for us whenever any Qt-wrapped module + // is loaded. + PyObject *pysideInitFilePath = PyObject_GetAttrString(pysideModule, "__file__"); + Py_DECREF(pysideModule); + if (!pysideInitFilePath) + return false; + + QString initPath = pyStringToQString(pysideInitFilePath); + Py_DECREF(pysideInitFilePath); + if (initPath.isEmpty()) + return false; + + // pysideDir - absolute path to the directory containing the init file, which also contains + // the rest of the PySide2 modules. + // prefixPath - absolute path to the directory containing the installed Qt (prefix). + QDir pysideDir = QFileInfo(QDir::fromNativeSeparators(initPath)).absoluteDir(); + QString setupPrefix; +#ifdef PYSIDE_QT_CONF_PREFIX + setupPrefix = QStringLiteral(PYSIDE_QT_CONF_PREFIX); +#endif + QString prefixPath = pysideDir.absoluteFilePath(setupPrefix); + + // rccData needs to be static, otherwise when it goes out of scope, the Qt resource system + // will point to invalid memory. + static QByteArray rccData = QByteArray("[Paths]\nPrefix = ") + prefixPath.toLocal8Bit(); + rccData.append('\n'); + + // The RCC data structure expects a 4-byte size value representing the actual data. + int size = rccData.size(); + + for (int i = 0; i < 4; ++i) { + rccData.prepend((size & 0xff)); + size >>= 8; + } + + const int version = 0x01; + isRegistered = qRegisterResourceData(version, qt_resource_struct, qt_resource_name, + reinterpret_cast<const unsigned char *>( + rccData.constData())); + + return isRegistered; +} + + + } //namespace PySide diff --git a/sources/pyside2/libpyside/pyside.h b/sources/pyside2/libpyside/pyside.h index 3619e2875..becb92208 100644 --- a/sources/pyside2/libpyside/pyside.h +++ b/sources/pyside2/libpyside/pyside.h @@ -150,6 +150,19 @@ PYSIDE_API QuickRegisterItemFunction getQuickRegisterItemFunction(); PYSIDE_API void setQuickRegisterItemFunction(QuickRegisterItemFunction function); #endif // PYSIDE_QML_SUPPORT +/** + * Given A PyObject repesenting ASCII or Unicode data, returns an equivalent QString. + */ +PYSIDE_API QString pyStringToQString(PyObject *str); + +/** + * Registers a dynamic "qt.conf" file with the Qt resource system. + * + * This is used in a standalone build, to inform QLibraryInfo of the Qt prefix (where Qt libraries + * are installed) so that plugins can be successfully loaded. + */ +PYSIDE_API bool registerInternalQtConf(); + } //namespace PySide diff --git a/sources/pyside2/tests/CMakeLists.txt b/sources/pyside2/tests/CMakeLists.txt index 2b7e3b0e1..34a2e2501 100644 --- a/sources/pyside2/tests/CMakeLists.txt +++ b/sources/pyside2/tests/CMakeLists.txt @@ -43,7 +43,7 @@ else() set_tests_properties(${TEST_NAME} PROPERTIES TIMEOUT ${CTEST_TESTING_TIMEOUT} WILL_FAIL ${EXPECT_TO_FAIL} - ENVIRONMENT "PYTHONPATH=${TEST_PYTHONPATH};${LIBRARY_PATH_VAR}=${TEST_LIBRARY_PATH}") + ENVIRONMENT "PYTHONPATH=${TEST_PYTHONPATH};${LIBRARY_PATH_VAR}=${TEST_LIBRARY_PATH};PYSIDE_DISABLE_INTERNAL_QT_CONF=1") endmacro() add_subdirectory(pysidetest) |