diff options
author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2018-10-16 13:26:40 +0200 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2018-10-16 13:27:27 +0200 |
commit | 7aff06b31b4bd8bb28cfedc0dcafbd1f32324cba (patch) | |
tree | dbd9d10d8f67243be79fea6d0e32536b79ba83b9 | |
parent | 0bb173c96a2be3d10a260bb7f001c7557047d392 (diff) | |
parent | 6bfbfd6edd0f9701664698768f9ec8d29f96a5bd (diff) |
Merge remote-tracking branch 'origin/5.12' into dev
Change-Id: I5ddbf8fb1d92be6dc0459164150f83dea375c69f
72 files changed, 3352 insertions, 1717 deletions
diff --git a/.gitignore b/.gitignore index 6a8b69288..903fc81f3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,6 @@ /build /dist /pyside*_build -/pyside*_package /pyside*_install /PySide /PySide-*.*.* @@ -13,3 +12,5 @@ distribute-*.egg distribute-*.tar.gz explore2 build_history/2* +*.qdocconf +*.qdocconf.in diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index dd79b2380..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,40 +0,0 @@ -# -# MANIFEST.in -# -# Manifest template for creating the PySide source distribution. - -include MANIFEST.in -include CHANGES.rst -include README.rst -include ez_setup.py -include setup.py -include popenasync.py -include qtinfo.py -include utils.py - -# sources -recursive-include sources/patchelf ** -recursive-include sources/shiboken2 ** -recursive-include sources/pyside2 ** -recursive-include sources/pyside2-tools ** -# ignore .git -recursive-exclude sources/shiboken2/.git ** -recursive-exclude sources/pyside2/.git ** -recursive-exclude sources/pyside2-tools/.git ** - -# PySide package -recursive-include pyside_package/PySide2 ** -recursive-include pyside_package/PySide2/docs ** -recursive-include pyside_package/PySide2/plugins ** -recursive-include pyside_package/PySide2s ** -recursive-include pyside_package/PySide2/translations ** -recursive-include pyside_package/PySide2include ** -recursive-include pyside_package/PySide2/typesystems ** -recursive-include pyside_package/PySide2/examples ** - -# pysideuic package -recursive-include pyside_package/pysideuic ** -recursive-include pyside_package/pysideuic/Compiler ** -recursive-include pyside_package/pysideuic/port_v2 ** -recursive-include pyside_package/pysideuic/port_v3 ** -recursive-include pyside_package/pysideuic/widget-plugins ** @@ -1,53 +1,9 @@ -# PySide2 +# Qt For Python -### Introduction - -PySide is the [Python Qt bindings project](http://wiki.qt.io/PySide2), providing +Qt For Python is the [Python Qt bindings project](http://wiki.qt.io/PySide2), providing access to the complete Qt 5.x framework as well as to generator tools for rapidly generating bindings for any C++ libraries. -The PySide project is developed in the open, with all facilities you'd expect -from any modern OSS project such as all code in a git repository and an open -design process. We welcome any contribution conforming to the -[Qt Contribution Agreement](https://www.qt.io/contributionagreement/). - - -PySide 2 supports Qt5. For building, please read about -[getting started](https://wiki.qt.io/PySide2_GettingStarted). -Then download the sources by running - - git clone https://code.qt.io/pyside/pyside-setup - -### Building - -#### Dependencies - -PySide versions following 5.6 use a C++ parser based on -[Clang](http://clang.org/). The Clang library (C-bindings), version 3.9 or -higher is required for building. Prebuilt versions of it can be downloaded from -[download.qt.io](http://download.qt.io/development_releases/prebuilt/libclang/). - -After unpacking the archive, set the environment variable *LLVM_INSTALL_DIR* to -point to the folder containing the *include* and *lib* directories of Clang: - - 7z x .../libclang-release_39-linux-Rhel7.2-gcc5.3-x86_64.7z - export LLVM_INSTALL_DIR=$PWD/libclang - -On Windows: - - 7z x .../libclang-release_39-windows-vs2015_64.7z - SET LLVM_INSTALL_DIR=%CD%\libclang - -#### Build Instructions - -You might consider using a virtual environment as described at -[getting started](https://wiki.qt.io/PySide2_GettingStarted). -You should be able to build: - - cd pyside-setup - python setup.py install +shiboken2 is the generator used to build the bindings. -The setup script will try to find the location of the qmake tool of the Qt -version to be used and the cmake build tool in the path. Non-standard -locations can be specified by the *--qmake=path_to_qmake* or -*--cmake=path_to_cmake* command line options. +See README.pyside2.md and README.shiboken2.md for details. diff --git a/README.pyside2.md b/README.pyside2.md new file mode 100644 index 000000000..cde05940d --- /dev/null +++ b/README.pyside2.md @@ -0,0 +1,53 @@ +# PySide2 + +### Introduction + +PySide is the [Python Qt bindings project](http://wiki.qt.io/PySide2), providing +access to the complete Qt 5.x framework as well as to generator tools for rapidly +generating bindings for any C++ libraries. + +The PySide project is developed in the open, with all facilities you'd expect +from any modern OSS project such as all code in a git repository and an open +design process. We welcome any contribution conforming to the +[Qt Contribution Agreement](https://www.qt.io/contributionagreement/). + + +PySide 2 supports Qt5. For building, please read about +[getting started](https://wiki.qt.io/PySide2_GettingStarted). +Then download the sources by running + + git clone https://code.qt.io/pyside/pyside-setup + +### Building + +#### Dependencies + +PySide versions following 5.6 use a C++ parser based on +[Clang](http://clang.org/). The Clang library (C-bindings), version 3.9 or +higher is required for building. Prebuilt versions of it can be downloaded from +[download.qt.io](http://download.qt.io/development_releases/prebuilt/libclang/). + +After unpacking the archive, set the environment variable *LLVM_INSTALL_DIR* to +point to the folder containing the *include* and *lib* directories of Clang: + + 7z x .../libclang-release_39-linux-Rhel7.2-gcc5.3-x86_64.7z + export LLVM_INSTALL_DIR=$PWD/libclang + +On Windows: + + 7z x .../libclang-release_39-windows-vs2015_64.7z + SET LLVM_INSTALL_DIR=%CD%\libclang + +#### Build Instructions + +You might consider using a virtual environment as described at +[getting started](https://wiki.qt.io/PySide2_GettingStarted). +You should be able to build: + + cd pyside-setup + python setup.py install + +The setup script will try to find the location of the qmake tool of the Qt +version to be used and the cmake build tool in the path. Non-standard +locations can be specified by the *--qmake=path_to_qmake* or +*--cmake=path_to_cmake* command line options. diff --git a/README.shiboken2-generator.md b/README.shiboken2-generator.md new file mode 100644 index 000000000..c71c6a8de --- /dev/null +++ b/README.shiboken2-generator.md @@ -0,0 +1 @@ +# shiboken2-generator diff --git a/README.shiboken2.md b/README.shiboken2.md new file mode 100644 index 000000000..f98f63c57 --- /dev/null +++ b/README.shiboken2.md @@ -0,0 +1 @@ +# shiboken2 module diff --git a/build_scripts/build_scripts.pyqtc b/build_scripts/build_scripts.pyqtc index eb4b58a63..1fc1c9664 100644 --- a/build_scripts/build_scripts.pyqtc +++ b/build_scripts/build_scripts.pyqtc @@ -1,8 +1,10 @@ __init__.py +config.py main.py options.py platforms qtinfo.py +setup_runner.py utils.py wheel_override.py platforms/__init__.py @@ -11,3 +13,6 @@ platforms/macos.py platforms/unix.py platforms/windows_desktop.py ../setup.py +../coin_build_instructions.py +../coin_test_instructions.py + diff --git a/build_scripts/config.py b/build_scripts/config.py new file mode 100644 index 000000000..78d7d4040 --- /dev/null +++ b/build_scripts/config.py @@ -0,0 +1,393 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys, os +import distutils.log as log + + +class Config(object): + def __init__(self): + # Constants + self._build_type_all = "all" + self._invocation_type_top_level = "top-level" + self._invocation_type_internal = "internal" + + # The keyword arguments which will be given to setuptools.setup + self.setup_kwargs = {} + + # The setup.py invocation type. + # top-level + # internal + self.invocation_type = None + + # The type of the top-level build. + # all - build shiboken2 module, shiboken2-generator and PySide2 + # modules + # shiboken2 - build only shiboken2 module + # shiboken2-generator - build only the shiboken2-generator + # pyside2 - build only PySide2 modules + self.build_type = None + + # The internal build type, used for internal invocations of + # setup.py to build a specific module only. + self.internal_build_type = None + + # Options that can be given to --build-type and + # --internal-build-type + self.shiboken_module_option_name = "shiboken2" + self.shiboken_generator_option_name = "shiboken2-generator" + self.pyside_option_name = "pyside2" + + # Names to be passed to setuptools.setup() name key, + # so not package name, but rather project name as it appears + # in the wheel name and on PyPi. + self.shiboken_module_st_name = "shiboken2" + self.shiboken_generator_st_name = "shiboken2-generator" + self.pyside_st_name = "PySide2" + + # Used by check_allowed_python_version to validate the + # interpreter version. + self.python_version_classifiers = [ + 'Programming Language :: Python', + 'Programming Language :: Python :: 2', + 'Programming Language :: Python :: 2.7', + 'Programming Language :: Python :: 3', + 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3.4', + 'Programming Language :: Python :: 3.5', + 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + ] + + self.setup_script_dir = None + + def init_config(self, build_type=None, internal_build_type=None, + cmd_class_dict=None, package_version=None, + ext_modules=None, setup_script_dir=None): + """ + Sets up the global singleton config which is used in many parts + of the setup process. + """ + + # if --internal-build-type was passed, it means that this is a + # sub-invocation to build a specific package. + if internal_build_type: + self.set_is_internal_invocation() + self.set_internal_build_type(internal_build_type) + else: + self.set_is_top_level_invocation() + + # --build-type was specified explicitly, so set it. Otherwise + # default to all. + if build_type: + self.build_type = build_type + else: + self.build_type = self._build_type_all + + self.setup_script_dir = setup_script_dir + + setup_kwargs = {} + setup_kwargs['long_description'] = self.get_long_description() + setup_kwargs['long_description_content_type'] = 'text/markdown', + setup_kwargs['keywords'] = 'Qt' + setup_kwargs['author'] = 'Qt for Python Team' + setup_kwargs['author_email'] = 'pyside@qt-project.org' + setup_kwargs['url'] = 'https://www.pyside.org' + setup_kwargs['download_url'] = 'https://download.qt.io/official_releases/QtForPython' + setup_kwargs['license'] = 'LGPL' + setup_kwargs['zip_safe'] = False + setup_kwargs['cmdclass'] = cmd_class_dict + setup_kwargs['version'] = package_version + + # Setting these two keys is still a bit of a discussion point. + # In general not setting them will allow using "build" and + # "bdist_wheel" just fine. What they do, is they specify to the + # setuptools.command.build_py command that certain pure python + # modules (.py files) exist in the specified package location, + # and that they should be copied over to the setuptools build + # dir. + # But it doesn't really make sense for us, because we copy all + # the necessary files to the build dir via prepare_packages() + # function anyway. + # If we don't set them, the build_py sub-command will be + # skipped, but the build command will still be executed, which + # is where we run cmake / make. + # The only plausible usage of it, is if we will implement a + # correctly functioning setup.py develop command (or bdist_egg). + # But currently that doesn't seem to work. + setup_kwargs['packages'] = self.get_setup_tools_packages_for_current_build() + setup_kwargs['package_dir'] = self.get_package_name_to_dir_path_mapping() + + # Add a bogus extension module (will never be built here since + # we are overriding the build command to do it using cmake) so + # things like bdist_egg will know that there are extension + # modules and will name the dist with the full platform info. + setup_kwargs['ext_modules'] = ext_modules + + common_classifiers = [ + 'Development Status :: 5 - Production/Stable', + 'Environment :: Console', + 'Environment :: MacOS X', + 'Environment :: X11 Applications :: Qt', + 'Environment :: Win32 (MS Windows)', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', + 'Operating System :: MacOS :: MacOS X', + 'Operating System :: POSIX', + 'Operating System :: POSIX :: Linux', + 'Operating System :: Microsoft', + 'Operating System :: Microsoft :: Windows', + 'Programming Language :: C++'] + common_classifiers.extend(self.python_version_classifiers) + common_classifiers.extend([ + 'Topic :: Database', + 'Topic :: Software Development', + 'Topic :: Software Development :: Code Generators', + 'Topic :: Software Development :: Libraries :: Application Frameworks', + 'Topic :: Software Development :: User Interfaces', + 'Topic :: Software Development :: Widget Sets']) + setup_kwargs['classifiers'] = common_classifiers + + if self.internal_build_type == self.shiboken_module_option_name: + setup_kwargs['name'] = self.shiboken_module_st_name + setup_kwargs['description'] = "Python / C++ bindings helper module", + setup_kwargs['entry_points'] = {} + + elif self.internal_build_type == self.shiboken_generator_option_name: + setup_kwargs['name'] = self.shiboken_generator_st_name + setup_kwargs['description'] = "Python / C++ bindings generator", + setup_kwargs['install_requires'] = [self.shiboken_module_st_name] + setup_kwargs['entry_points'] = { + 'console_scripts': [ + 'shiboken2 = {}.scripts.shiboken_tool:main'.format(self.package_name()), + ] + } + + elif self.internal_build_type == self.pyside_option_name: + setup_kwargs['name'] = self.pyside_st_name + setup_kwargs['description'] = ("Python bindings for the Qt cross-platform application" + " and UI framework"), + setup_kwargs['install_requires'] = [self.shiboken_module_st_name] + setup_kwargs['entry_points'] = { + 'console_scripts': [ + 'pyside2-uic = {}.scripts.uic:main'.format(self.package_name()), + 'pyside2-rcc = {}.scripts.pyside_tool:main'.format(self.package_name()), + 'pyside2-lupdate = {}.scripts.pyside_tool:main'.format(self.package_name()), + ] + } + self.setup_kwargs = setup_kwargs + + def get_long_description(self): + readme_filename = 'README.md' + changes_filename = 'CHANGES.rst' + + if self.is_internal_shiboken_module_build(): + readme_filename = 'README.shiboken2.md' + elif self.is_internal_shiboken_generator_build(): + readme_filename = 'README.shiboken2-generator.md' + elif self.is_internal_pyside_build(): + readme_filename = 'README.pyside2.md' + + content = '' + changes = '' + try: + with open(os.path.join(self.setup_script_dir, readme_filename)) as f: + readme = f.read() + except Exception as e: + log.error("Couldn't read contents of {}.".format(readme_filename)) + raise + + # Don't include CHANGES.rst for now, because we have not decided + # how to handle change files yet. + include_changes = False + if include_changes: + try: + with open(os.path.join(self.setup_script_dir, changes_filename)) as f: + changes = f.read() + except Exception as e: + log.error("Couldn't read contents of {}".format(changes_filename)) + raise + content += readme + + if changes: + content += "\n\n" + changes + + return content + + def package_name(self): + """ + Returns package name as it appears in Python's site-packages + directory. + + Package names can only be delimited by underscores, and not by + dashes. + """ + if self.is_internal_shiboken_module_build(): + return "shiboken2" + elif self.is_internal_shiboken_generator_build(): + return "shiboken2_generator" + elif self.is_internal_pyside_build(): + return "PySide2" + else: + return None + + def get_setup_tools_packages_for_current_build(self): + """ + Returns a list of packages for setup tools to consider in the + build_py command, so that it can copy the pure python files. + Not really necessary because it's done in prepare_packages() + anyway. + + This is really just to satisfy some checks in setuptools + build_py command, and if we ever properly implement the develop + command. + """ + if self.internal_build_type == self.pyside_option_name: + return [ + config.package_name(), + 'pyside2uic', + 'pyside2uic.Compiler', + 'pyside2uic.port_v{}'.format(sys.version_info[0]) + ] + elif self.internal_build_type == self.shiboken_module_option_name: + return [self.package_name()] + else: + return [] + + def get_package_name_to_dir_path_mapping(self): + """ + Used in setuptools.setup 'package_dir' argument to specify where + the actual module packages are located. + + For example when building the shiboken module, setuptools will + expect to find the "shiboken2" module sources under + "sources/shiboken2/shibokenmodule". + + This is really just to satisfy some checks in setuptools + build_py command, and if we ever properly implement the develop + command. + """ + if self.is_internal_shiboken_module_build(): + return { + self.package_name(): "sources/shiboken2/shibokenmodule" + } + elif self.is_internal_shiboken_generator_build(): + # This is left empty on purpose, because the shiboken + # generator doesn't have a python module for now. + return {} + elif self.is_internal_pyside_build(): + return { + self.package_name(): "sources/pyside2/PySide2", + "pyside2uic": "sources/pyside2-tools/pyside2uic" + } + else: + return {} + + def get_buildable_extensions(self): + """ + Used by PysideBuild.run to build the CMake projects. + :return: A list of directory names under the sources directory. + """ + if self.is_internal_shiboken_module_build() or self.is_internal_shiboken_generator_build(): + return ['shiboken2'] + elif self.is_internal_pyside_build(): + return ['pyside2', 'pyside2-tools'] + return None + + def set_is_top_level_invocation(self): + self.invocation_type = self._invocation_type_top_level + + def set_is_internal_invocation(self): + self.invocation_type = self._invocation_type_internal + + def is_top_level_invocation(self): + return self.invocation_type == self._invocation_type_top_level + + def is_internal_invocation(self): + return self.invocation_type == self._invocation_type_internal + + def is_top_level_build_all(self): + return self.build_type == self._build_type_all + + def is_top_level_build_shiboken_module(self): + return self.build_type == self.shiboken_module_option_name + + def is_top_level_build_shiboken_generator(self): + return self.build_type == self.shiboken_generator_option_name + + def is_top_level_build_pyside(self): + return self.build_type == self.pyside_option_name + + def set_internal_build_type(self, internal_build_type): + self.internal_build_type = internal_build_type + + def is_internal_shiboken_module_build(self): + return self.internal_build_type == self.shiboken_module_option_name + + def is_internal_shiboken_generator_build(self): + return self.internal_build_type == self.shiboken_generator_option_name + + def is_internal_pyside_build(self): + return self.internal_build_type == self.pyside_option_name + + def is_internal_shiboken_generator_build_and_part_of_top_level_all(self): + """ + Used to skip certain build rules and output, when we know that + the CMake build of shiboken was already done as part of the + top-level "all" build when shiboken2-module was built. + """ + return self.is_internal_shiboken_generator_build() and self.is_top_level_build_all() + + def get_allowed_top_level_build_values(self): + return [ + self._build_type_all, + self.shiboken_module_option_name, + self.shiboken_generator_option_name, + self.pyside_option_name + ] + + def get_allowed_internal_build_values(self): + return [ + self.shiboken_module_option_name, + self.shiboken_generator_option_name, + self.pyside_option_name + ] + + +config = Config() diff --git a/build_scripts/main.py b/build_scripts/main.py index b64d6f1a9..50f751caa 100644 --- a/build_scripts/main.py +++ b/build_scripts/main.py @@ -42,6 +42,7 @@ from distutils.version import LooseVersion import os import time +from .config import config from .utils import memoize, get_python_dict from .options import * @@ -90,15 +91,11 @@ def get_setuptools_extension_modules(): extension_modules = [Extension(*extension_args, **extension_kwargs)] return extension_modules -# Buildable extensions. -contained_modules = ['shiboken2', 'pyside2', 'pyside2-tools'] # Git submodules: ["submodule_name", # "location_relative_to_sources_folder"] submodules = [["pyside2-tools"]] -pyside_package_dir_name = "pyside_package" - try: import setuptools except ImportError: @@ -108,13 +105,8 @@ except ImportError: import sys import platform import re -import fnmatch - -import difflib # for a close match of dirname and module -import functools -from distutils import log -from distutils.errors import DistutilsOptionError +import distutils.log as log from distutils.errors import DistutilsSetupError from distutils.sysconfig import get_config_var from distutils.sysconfig import get_python_lib @@ -123,7 +115,7 @@ from distutils.command.build import build as _build from distutils.command.build_ext import build_ext as _build_ext from distutils.util import get_platform -from setuptools import setup, Extension +from setuptools import Extension from setuptools.command.install import install as _install from setuptools.command.install_lib import install_lib as _install_lib from setuptools.command.bdist_egg import bdist_egg as _bdist_egg @@ -132,39 +124,40 @@ from setuptools.command.build_py import build_py as _build_py from .qtinfo import QtInfo from .utils import rmtree, detect_clang, copyfile, copydir, run_process_output, run_process -from .utils import update_env_path, init_msvc_env, filter_match, macos_fix_rpaths_for_library +from .utils import update_env_path, init_msvc_env, filter_match +from .utils import macos_fix_rpaths_for_library +from .utils import linux_fix_rpaths_for_library from .platforms.unix import prepare_packages_posix from .platforms.windows_desktop import prepare_packages_win32 from .wheel_override import wheel_module_exists, get_bdist_wheel_override from textwrap import dedent -# make sure that setup.py is run with an allowed python version + def check_allowed_python_version(): + """ + Make sure that setup.py is run with an allowed python version. + """ + import re - pattern = "'Programming Language :: Python :: (\d+)\.(\d+)'" + pattern = "Programming Language :: Python :: (\d+)\.(\d+)" supported = [] - with open(setup_py_path) as setup: - for line in setup.readlines(): - found = re.search(pattern, line) - if found: - major = int(found.group(1)) - minor = int(found.group(2)) - supported.append( (major, minor) ) + + for line in config.python_version_classifiers: + found = re.search(pattern, line) + if found: + major = int(found.group(1)) + minor = int(found.group(2)) + supported.append( (major, minor) ) this_py = sys.version_info[:2] if this_py not in supported: - print("only these python versions are supported:", supported) + print("Unsupported python version detected. Only these python versions are supported: {}" + .format(supported)) sys.exit(1) -check_allowed_python_version() qt_src_dir = '' -# This is used automatically by distutils.command.install object, to -# specify final installation location. -OPTION_FINAL_INSTALL_PREFIX = option_value("prefix") - - if OPTION_QT_VERSION is None: OPTION_QT_VERSION = "5" if OPTION_QMAKE is None: @@ -293,8 +286,7 @@ def get_qt_version(): qt_version = qtinfo.version if not qt_version: - log.error("Failed to query the Qt version with qmake {0}".format( - self.qtinfo.qmake_command)) + log.error("Failed to query the Qt version with qmake {0}".format(qtinfo.qmake_command)) sys.exit(1) if LooseVersion(qtinfo.version) < LooseVersion("5.7"): @@ -304,12 +296,13 @@ def get_qt_version(): return qt_version + def prepare_build(): if (os.path.isdir(".git") and not OPTION_IGNOREGIT and not OPTION_ONLYPACKAGE and not OPTION_REUSE_BUILD): prepare_sub_modules() - # Clean up temp and package folders - for n in [pyside_package_dir_name, "build"]: + # Clean up temp build folder. + for n in ["build"]: d = os.path.join(setup_script_dir, n) if os.path.isdir(d): print("Removing {}".format(d)) @@ -318,13 +311,7 @@ def prepare_build(): except Exception as e: print('***** problem removing "{}"'.format(d)) print('ignored error: {}'.format(e)) - # Prepare package folders - ppdn = pyside_package_dir_name - absolute_paths = [os.path.join(ppdn, "PySide2"), - os.path.join(ppdn, "pyside2uic")] - for pkg in absolute_paths: - pkg_dir = os.path.join(setup_script_dir, pkg) - os.makedirs(pkg_dir) + # locate Qt sources for the documentation if OPTION_QT_SRC is None: install_prefix = qtinfo.prefix_dir @@ -389,26 +376,15 @@ class PysideBuildExt(_build_ext): pass - -# pyside_build_py and pyside_install_lib are reimplemented to preserve -# symlinks when distutils / setuptools copy files to various -# directories through the different build stages. class PysideBuildPy(_build_py): def __init__(self, *args, **kwargs): _build_py.__init__(self, *args, **kwargs) - def build_package_data(self): - """Copies files from pyside_package into build/xxx directory""" - - for package, src_dir, build_dir, filenames in self.data_files: - for filename in filenames: - target = os.path.join(build_dir, filename) - self.mkpath(os.path.dirname(target)) - srcfile = os.path.abspath(os.path.join(src_dir, filename)) - # Using our own copyfile makes sure to preserve symlinks. - copyfile(srcfile, target) +# _install_lib is reimplemented to preserve +# symlinks when distutils / setuptools copy files to various +# directories from the setup tools build dir to the install dir. class PysideInstallLib(_install_lib): def __init__(self, *args, **kwargs): @@ -539,10 +515,12 @@ class PysideBuild(_build): py_prefix = get_config_var("prefix") if not py_prefix or not os.path.exists(py_prefix): py_prefix = sys.prefix + self.py_prefix = py_prefix if sys.platform == "win32": py_scripts_dir = os.path.join(py_prefix, "Scripts") else: py_scripts_dir = os.path.join(py_prefix, "bin") + self.py_scripts_dir = py_scripts_dir if py_libdir is None or not os.path.exists(py_libdir): if sys.platform == "win32": py_libdir = os.path.join(py_prefix, "libs") @@ -656,7 +634,7 @@ class PysideBuild(_build): qt_version = get_qt_version() # Update the PATH environment variable - additional_paths = [py_scripts_dir, qt_dir] + additional_paths = [self.py_scripts_dir, qt_dir] # Add Clang to path for Windows. # Revisit once Clang is bundled with Qt. @@ -685,19 +663,11 @@ class PysideBuild(_build): install_dir = os.path.join(script_dir, prefix() + "_install", "{}".format(build_name)) - # Try to ensure that tools built by this script (such as shiboken2) - # are found before any that may already be installed on the system. - update_env_path([os.path.join(install_dir, 'bin')]) - - # Tell cmake to look here for *.cmake files - os.environ['CMAKE_PREFIX_PATH'] = install_dir - self.make_path = make_path self.make_generator = make_generator self.debug = OPTION_DEBUG self.script_dir = script_dir - self.pyside_package_dir = os.path.join(self.script_dir, - pyside_package_dir_name) + self.st_build_dir = os.path.join(self.script_dir, self.build_lib) self.sources_dir = sources_dir self.build_dir = build_dir self.install_dir = install_dir @@ -709,14 +679,61 @@ class PysideBuild(_build): self.site_packages_dir = get_python_lib(1, 0, prefix=install_dir) self.build_tests = OPTION_BUILDTESTS - setuptools_install_prefix = get_python_lib(1) - if OPTION_FINAL_INSTALL_PREFIX: - setuptools_install_prefix = OPTION_FINAL_INSTALL_PREFIX - # Save the shiboken build dir path for clang deployment # purposes. self.shiboken_build_dir = os.path.join(self.build_dir, "shiboken2") + self.log_pre_build_info() + + # Prepare folders + if not os.path.exists(self.sources_dir): + log.info("Creating sources folder {}...".format(self.sources_dir)) + os.makedirs(self.sources_dir) + if not os.path.exists(self.build_dir): + log.info("Creating build folder {}...".format(self.build_dir)) + os.makedirs(self.build_dir) + if not os.path.exists(self.install_dir): + log.info("Creating install folder {}...".format(self.install_dir)) + os.makedirs(self.install_dir) + + if not (OPTION_ONLYPACKAGE + and not config.is_internal_shiboken_generator_build_and_part_of_top_level_all()): + # Build extensions + for ext in config.get_buildable_extensions(): + self.build_extension(ext) + + if OPTION_BUILDTESTS: + # we record the latest successful build and note the + # build directory for supporting the tests. + timestamp = time.strftime('%Y-%m-%d_%H%M%S') + build_history = os.path.join(setup_script_dir, 'build_history') + unique_dir = os.path.join(build_history, timestamp) + os.makedirs(unique_dir) + fpath = os.path.join(unique_dir, 'build_dir.txt') + with open(fpath, 'w') as f: + print(build_dir, file=f) + log.info("Created {}".format(build_history)) + + if not OPTION_SKIP_PACKAGING: + # Build patchelf if needed + self.build_patchelf() + + # Prepare packages + self.prepare_packages() + + # Build packages + _build.run(self) + else: + log.info("Skipped preparing and building packages.") + log.info('*** Build completed') + + def log_pre_build_info(self): + if config.is_internal_shiboken_generator_build_and_part_of_top_level_all(): + return + + setuptools_install_prefix = get_python_lib(1) + if OPTION_FINAL_INSTALL_PREFIX: + setuptools_install_prefix = OPTION_FINAL_INSTALL_PREFIX log.info("=" * 30) log.info("Package version: {}".format(get_package_version())) log.info("Build type: {}".format(self.build_type)) @@ -726,40 +743,34 @@ class PysideBuild(_build): log.info("Make generator: {}".format(self.make_generator)) log.info("Make jobs: {}".format(OPTION_JOBS)) log.info("-" * 3) - log.info("setup.py directory: {}".format(self.script_dir)) log.info("Build scripts directory: {}".format(build_scripts_dir)) log.info("Sources directory: {}".format(self.sources_dir)) - log.info(dedent(""" - Building PySide2 will create and touch directories + Building {st_package_name} will create and touch directories in the following order: make build directory (py*_build/*/*) -> make install directory (py*_install/*/*) -> - {} directory (pyside_package/*) -> setuptools build directory (build/*/*) -> setuptools install directory (usually path-installed-python/lib/python*/site-packages/*) - """).format(pyside_package_dir_name)) - + """).format(st_package_name=config.package_name())) log.info("make build directory: {}".format(self.build_dir)) log.info("make install directory: {}".format(self.install_dir)) - log.info("{} directory: {}".format(pyside_package_dir_name, - self.pyside_package_dir)) - log.info("setuptools build directory: {}".format( - os.path.join(self.script_dir, "build"))) - log.info("setuptools install directory: {}".format( - setuptools_install_prefix)) - log.info("make-installed site-packages directory: {} \n" - " (only relevant for copying files from " - "'make install directory' to '{} directory'".format( - self.site_packages_dir, pyside_package_dir_name)) + log.info("setuptools build directory: {}".format(self.st_build_dir)) + log.info("setuptools install directory: {}".format(setuptools_install_prefix)) + log.info(dedent(""" + make-installed site-packages directory: {} + (only relevant for copying files from 'make install directory' + to 'setuptools build directory' + """).format( + self.site_packages_dir)) log.info("-" * 3) log.info("Python executable: {}".format(self.py_executable)) log.info("Python includes: {}".format(self.py_include_dir)) log.info("Python library: {}".format(self.py_library)) - log.info("Python prefix: {}".format(py_prefix)) - log.info("Python scripts: {}".format(py_scripts_dir)) + log.info("Python prefix: {}".format(self.py_prefix)) + log.info("Python scripts: {}".format(self.py_scripts_dir)) log.info("-" * 3) log.info("Qt qmake: {}".format(self.qtinfo.qmake_command)) log.info("Qt version: {}".format(self.qtinfo.version)) @@ -772,52 +783,11 @@ class PysideBuild(_build): if sys.platform == 'darwin': pyside_macos_deployment_target = ( PysideBuild.macos_pyside_min_deployment_target() - ) + ) log.info("MACOSX_DEPLOYMENT_TARGET set to: {}".format( pyside_macos_deployment_target)) log.info("=" * 30) - # Prepare folders - if not os.path.exists(self.sources_dir): - log.info("Creating sources folder {}...".format(self.sources_dir)) - os.makedirs(self.sources_dir) - if not os.path.exists(self.build_dir): - log.info("Creating build folder {}...".format(self.build_dir)) - os.makedirs(self.build_dir) - if not os.path.exists(self.install_dir): - log.info("Creating install folder {}...".format(self.install_dir)) - os.makedirs(self.install_dir) - - if not OPTION_ONLYPACKAGE: - # Build extensions - for ext in contained_modules: - self.build_extension(ext) - - if OPTION_BUILDTESTS: - # we record the latest successful build and note the - # build directory for supporting the tests. - timestamp = time.strftime('%Y-%m-%d_%H%M%S') - build_history = os.path.join(setup_script_dir, 'build_history') - unique_dir = os.path.join(build_history, timestamp) - os.makedirs(unique_dir) - fpath = os.path.join(unique_dir, 'build_dir.txt') - with open(fpath, 'w') as f: - print(build_dir, file=f) - log.info("Created {}".format(build_history)) - - if not OPTION_SKIP_PACKAGING: - # Build patchelf if needed - self.build_patchelf() - - # Prepare packages - self.prepare_packages() - - # Build packages - _build.run(self) - else: - log.info("Skipped preparing and building packages.") - log.info('*** Build completed') - @staticmethod def macos_qt_min_deployment_target(): target = qtinfo.macos_min_deployment_target @@ -953,6 +923,17 @@ class PysideBuild(_build): cmake_cmd.append("-DPYTHON_EXECUTABLE={}".format(self.py_executable)) cmake_cmd.append("-DPYTHON_INCLUDE_DIR={}".format(self.py_include_dir)) cmake_cmd.append("-DPYTHON_LIBRARY={}".format(self.py_library)) + + # If a custom shiboken cmake config directory path was provided, pass it to CMake. + if OPTION_SHIBOKEN_CONFIG_DIR and config.is_internal_pyside_build(): + if os.path.exists(OPTION_SHIBOKEN_CONFIG_DIR): + log.info("Using custom provided shiboken2 installation: {}" + .format(OPTION_SHIBOKEN_CONFIG_DIR)) + cmake_cmd.append("-DShiboken2_DIR={}".format(OPTION_SHIBOKEN_CONFIG_DIR)) + else: + log.info("Custom provided shiboken2 installation not found. Path given: {}" + .format(OPTION_SHIBOKEN_CONFIG_DIR)) + if OPTION_MODULE_SUBSET: module_sub_set = '' for m in OPTION_MODULE_SUBSET.split(','): @@ -1014,20 +995,20 @@ class PysideBuild(_build): cmake_cmd.append("-DPYSIDE_QT_CONF_PREFIX={}".format( pyside_qt_conf_prefix)) - # Pass package version to CMake, so this string can be - # embedded into _config.py file. - package_version = get_package_version() - cmake_cmd.append("-DPYSIDE_SETUP_PY_PACKAGE_VERSION={}".format( - package_version)) - - # In case if this is a snapshot build, also pass the - # timestamp as a separate value, because it the only - # version component that is actually generated by setup.py. - timestamp = '' - if OPTION_SNAPSHOT_BUILD: - timestamp = get_package_timestamp() - cmake_cmd.append("-DPYSIDE_SETUP_PY_PACKAGE_TIMESTAMP={}".format( - timestamp)) + # Pass package version to CMake, so this string can be + # embedded into _config.py file. + package_version = get_package_version() + cmake_cmd.append("-DPACKAGE_SETUP_PY_PACKAGE_VERSION={}".format( + package_version)) + + # In case if this is a snapshot build, also pass the + # timestamp as a separate value, because it is the only + # version component that is actually generated by setup.py. + timestamp = '' + if OPTION_SNAPSHOT_BUILD: + timestamp = get_package_timestamp() + cmake_cmd.append("-DPACKAGE_SETUP_PY_PACKAGE_TIMESTAMP={}".format( + timestamp)) if extension.lower() in ["shiboken2", "pyside2-tools"]: cmake_cmd.append("-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=yes") @@ -1129,15 +1110,24 @@ class PysideBuild(_build): os.chdir(self.script_dir) def prepare_packages(self): + """ + This will copy all relevant files from the various locations in the "cmake install dir", + to the setup tools build dir (which is read from self.build_lib provided by distutils). + + After that setuptools.command.build_py is smart enough to copy everything + from the build dir to the install dir (the virtualenv site-packages for example). + """ try: - log.info("Preparing packages...") + log.info("\nPreparing setup tools build directory.\n") vars = { "site_packages_dir": self.site_packages_dir, "sources_dir": self.sources_dir, "install_dir": self.install_dir, "build_dir": self.build_dir, "script_dir": self.script_dir, - "pyside_package_dir": self.pyside_package_dir, + "st_build_dir": self.st_build_dir, + "cmake_package_name": config.package_name(), + "st_package_name": config.package_name(), "ssl_libs_dir": OPTION_OPENSSL, "py_version": self.py_version, "qt_version": self.qtinfo.version, @@ -1151,6 +1141,12 @@ class PysideBuild(_build): "qt_qml_dir": self.qtinfo.qml_dir, "target_arch": self.py_arch, } + + # Needed for correct file installation in generator build + # case. + if config.is_internal_shiboken_generator_build(): + vars['cmake_package_name'] = config.shiboken_module_option_name + os.chdir(self.script_dir) if sys.platform == "win32": @@ -1170,19 +1166,21 @@ class PysideBuild(_build): def get_built_pyside_config(self, vars): # Get config that contains list of built modules, and # SOVERSIONs of the built libraries. - pyside_package_dir = vars['pyside_package_dir'] - config_path = os.path.join(pyside_package_dir, "PySide2", "_config.py") - config = get_python_dict(config_path) - return config + st_build_dir = vars['st_build_dir'] + config_path = os.path.join(st_build_dir, config.package_name(), "_config.py") + temp_config = get_python_dict(config_path) + if 'built_modules' not in temp_config: + temp_config['built_modules'] = [] + return temp_config def is_webengine_built(self, built_modules): return ('WebEngineWidgets' in built_modules or 'WebEngineCore' in built_modules or 'WebEngine' in built_modules) - def prepare_standalone_clang(self, is_win = False): + def prepare_standalone_clang(self, is_win=False): """ - Copies the libclang library to the pyside package so that - shiboken executable works. + Copies the libclang library to the shiboken2-generator + package so that the shiboken executable works. """ log.info('Finding path to the libclang shared library.') cmake_cmd = [ @@ -1205,47 +1203,54 @@ class PysideBuild(_build): if not clang_lib_path: raise RuntimeError("Could not find the location of the libclang " - "library inside the CMake cache file.") + "library inside the CMake cache file.") - target_name = None if is_win: # clang_lib_path points to the static import library # (lib/libclang.lib), whereas we want to copy the shared # library (bin/libclang.dll). - clang_lib_path = re.sub(r'lib/libclang.lib$', 'bin/libclang.dll', - clang_lib_path) + clang_lib_path = re.sub(r'lib/libclang.lib$', + 'bin/libclang.dll', + clang_lib_path) else: - if sys.platform != 'darwin' and os.path.islink(clang_lib_path): - # On Linux, we get "libclang.so" from CMake which is - # a symlink: - # libclang.so -> libclang.so.6 -> libclang.so.6.0. - # shiboken2 links against libclang.so.6. So, we - # determine the target name by resolving just - # one symlink (note: os.path.realpath() resolves all). - target_name = os.readlink(clang_lib_path) - # We want to resolve any symlink on Linux and macOS, and - # copy the actual file. - clang_lib_path = os.path.realpath(clang_lib_path) - - if not target_name: - target_name = os.path.basename(clang_lib_path) - - # Path to directory containing libclang. - clang_lib_dir = os.path.dirname(clang_lib_path) - - # The destination will be the package folder near the other - # extension modules. - destination_dir = "{}/PySide2".format(os.path.join(self.script_dir, - 'pyside_package')) + # shiboken2 links against libclang.so.6 or a similarly + # named library. + # If the linked against library is a symlink, resolve + # the symlink once (but not all the way to the real + # file) on Linux and macOS, + # so that we get the path to the "SO version" symlink + # (the one used as the install name in the shared library + # dependency section). + # E.g. On Linux libclang.so -> libclang.so.6 -> + # libclang.so.6.0. + # "libclang.so.6" is the name we want for the copied file. + if os.path.islink(clang_lib_path): + link_target = os.readlink(clang_lib_path) + if os.path.isabs(link_target): + clang_lib_path = link_target + else: + # link_target is relative, transform to absolute. + clang_lib_path = os.path.join(os.path.dirname(clang_lib_path), link_target) + clang_lib_path = os.path.abspath(clang_lib_path) + + # The destination will be the shiboken package folder. + vars = {} + vars['st_build_dir'] = self.st_build_dir + vars['st_package_name'] = config.package_name() + destination_dir = "{st_build_dir}/{st_package_name}".format(**vars) + if os.path.exists(clang_lib_path): - log.info('Copying libclang shared library {} to the package folder as {}.'.format( - clang_lib_path, target_name)) basename = os.path.basename(clang_lib_path) - destination_path = os.path.join(destination_dir, target_name) + log.info('Copying libclang shared library {} to the package folder as {}.'.format( + clang_lib_path, basename)) + destination_path = os.path.join(destination_dir, basename) # Need to modify permissions in case file is not writable # (a reinstall would cause a permission denied error). - copyfile(clang_lib_path, destination_path, make_writable_by_owner=True) + copyfile(clang_lib_path, + destination_path, + force_copy_symlink=True, + make_writable_by_owner=True) else: raise RuntimeError("Error copying libclang library " "from {} to {}. ".format( @@ -1265,18 +1270,17 @@ class PysideBuild(_build): else: # Add rpath values pointing to $ORIGIN and the # installed qt lib directory. - local_rpath = '$ORIGIN/' - qt_lib_dir = self.qtinfo.libs_dir + final_rpath = self.qtinfo.libs_dir if OPTION_STANDALONE: - qt_lib_dir = "$ORIGIN/Qt/lib" - final_rpath = local_rpath + ':' + qt_lib_dir - cmd = [self._patchelf_path, '--set-rpath', final_rpath, srcpath] - if run_process(cmd) != 0: - raise RuntimeError("Error patching rpath in " + srcpath) + final_rpath = "$ORIGIN/Qt/lib" + override = OPTION_STANDALONE + linux_fix_rpaths_for_library(self._patchelf_path, srcpath, final_rpath, + override=override) elif sys.platform == 'darwin': pyside_libs = [lib for lib in os.listdir( package_path) if filter_match(lib, ["*.so", "*.dylib"])] + def rpath_cmd(srcpath): final_rpath = '' # Command line rpath option takes precedence over @@ -1308,15 +1312,6 @@ class PysideBuild(_build): "updated rpath (OS/X) in {}.".format(srcpath)) -try: - with open(os.path.join(setup_script_dir, 'README.rst')) as f: - README = f.read() - with open(os.path.join(setup_script_dir, 'CHANGES.rst')) as f: - CHANGES = f.read() -except IOError: - README = CHANGES = '' - - cmd_class_dict = { 'build': PysideBuild, 'build_py': PysideBuildPy, diff --git a/build_scripts/options.py b/build_scripts/options.py index fd8b0718e..daf3bb00e 100644 --- a/build_scripts/options.py +++ b/build_scripts/options.py @@ -38,16 +38,88 @@ ############################################################################# from __future__ import print_function +import sys +import os + + +class Options(object): + def __init__(self): + + # Dictionary containing values of all the possible options. + self.dict = {} + + def has_option(self, name): + """ Returns True if argument '--name' was passed on the command + line. """ + try: + sys.argv.remove("--{}".format(name)) + self.dict[name] = True + return True + except ValueError: + pass + return False + + def option_value(self, name, remove=True): + """ + Returns the value of a command line option or environment + variable. + + :param name: The name of the command line option or environment + variable. + + :param remove: Whether the option and its value should be + removed from sys.argv. Useful when there's a need to query for + the value and also pass it along to setuptools for example. + + :return: Either the option value or None. + """ + for index, option in enumerate(sys.argv): + if option == '--' + name: + if index + 1 >= len(sys.argv): + raise RuntimeError("The option {} requires a value".format(option)) + value = sys.argv[index + 1] + + if remove: + sys.argv[index:index + 2] = [] + + self.dict[name] = value + return value + + if option.startswith('--' + name + '='): + value = option[len(name) + 3:] + + if remove: + sys.argv[index:index + 1] = [] + + self.dict[name] = value + return value + + env_val = os.getenv(name.upper().replace('-', '_')) + self.dict[name] = env_val + return env_val + + +options = Options() + + +def has_option(name): + return options.has_option(name) + + +def option_value(*args,**kwargs): + return options.option_value(*args,**kwargs) -from .utils import has_option, option_value # Declare options +OPTION_BUILD_TYPE = option_value("build-type") +OPTION_INTERNAL_BUILD_TYPE = option_value("internal-build-type") OPTION_DEBUG = has_option("debug") OPTION_RELWITHDEBINFO = has_option('relwithdebinfo') OPTION_QMAKE = option_value("qmake") OPTION_QT_VERSION = option_value("qt") OPTION_CMAKE = option_value("cmake") OPTION_OPENSSL = option_value("openssl") +OPTION_SHIBOKEN_CONFIG_DIR = option_value("shiboken-config-dir") OPTION_ONLYPACKAGE = has_option("only-package") OPTION_STANDALONE = has_option("standalone") OPTION_MAKESPEC = option_value("make-spec") @@ -82,3 +154,7 @@ OPTION_SANITIZE_ADDRESS = has_option("sanitize-address") OPTION_SNAPSHOT_BUILD = has_option("snapshot-build") OPTION_LIMITED_API = option_value("limited-api") OPTION_PACKAGE_TIMESTAMP = option_value("package-timestamp") + +# This is used automatically by distutils.command.install object, to +# specify the final installation location. +OPTION_FINAL_INSTALL_PREFIX = option_value("prefix", remove=False) diff --git a/build_scripts/platforms/linux.py b/build_scripts/platforms/linux.py index 4c38fef6c..067179cdc 100644 --- a/build_scripts/platforms/linux.py +++ b/build_scripts/platforms/linux.py @@ -37,76 +37,100 @@ ## ############################################################################# -from ..options import * from ..utils import copydir, copyfile, copy_icu_libs, find_files_using_glob +from ..config import config -def prepare_standalone_package_linux(self, executables, vars): + +def prepare_standalone_package_linux(self, vars): built_modules = vars['built_modules'] - # <qt>/lib/* -> <setup>/PySide2/Qt/lib - destination_lib_dir = "{pyside_package_dir}/PySide2/Qt/lib" + constrain_modules = None + copy_plugins = True + copy_qml = True + copy_translations = True + copy_qt_conf = True + should_copy_icu_libs = True + + if config.is_internal_shiboken_generator_build(): + constrain_modules = ["Core", "Network", "Xml", "XmlPatterns"] + copy_plugins = False + copy_qml = False + copy_translations = False + copy_qt_conf = False + should_copy_icu_libs = False + + # <qt>/lib/* -> <setup>/{st_package_name}/Qt/lib + destination_lib_dir = "{st_build_dir}/{st_package_name}/Qt/lib" + + accepted_modules = ['libQt5*.so.?'] + if constrain_modules: + accepted_modules = ["libQt5" + module + "*.so.?" for module in constrain_modules] + accepted_modules.append("libicu*.so.??") + copydir("{qt_lib_dir}", destination_lib_dir, - filter=[ - "libQt5*.so.?", - "libicu*.so.??", - ], - recursive=False, vars=vars, force_copy_symlinks=True) - - # Check if ICU libraries were copied over to the destination - # Qt libdir. - resolved_destination_lib_dir = destination_lib_dir.format(**vars) - maybe_icu_libs = find_files_using_glob(resolved_destination_lib_dir, - "libicu*") - - # If no ICU libraries are present in the Qt libdir (like when - # Qt is built against system ICU, or in the Coin CI where ICU - # libs are in a different directory) try to find out / resolve - # which ICU libs are used by QtCore (if used at all) using a - # custom written ldd, and copy the ICU libs to the Pyside Qt - # dir if necessary. We choose the QtCore lib to inspect, by - # checking which QtCore library the shiboken2 executable uses. - if not maybe_icu_libs: - copy_icu_libs(self._patchelf_path, resolved_destination_lib_dir) + filter=accepted_modules, + recursive=False, vars=vars, force_copy_symlinks=True) + + if should_copy_icu_libs: + # Check if ICU libraries were copied over to the destination + # Qt libdir. + resolved_destination_lib_dir = destination_lib_dir.format(**vars) + maybe_icu_libs = find_files_using_glob(resolved_destination_lib_dir, + "libicu*") + + # If no ICU libraries are present in the Qt libdir (like when + # Qt is built against system ICU, or in the Coin CI where ICU + # libs are in a different directory) try to find out / resolve + # which ICU libs are used by QtCore (if used at all) using a + # custom written ldd, and copy the ICU libs to the Pyside Qt + # dir if necessary. We choose the QtCore lib to inspect, by + # checking which QtCore library the shiboken2 executable uses. + if not maybe_icu_libs: + copy_icu_libs(self._patchelf_path, resolved_destination_lib_dir) if self.is_webengine_built(built_modules): copydir("{qt_lib_execs_dir}", - "{pyside_package_dir}/PySide2/Qt/libexec", + "{st_build_dir}/{st_package_name}/Qt/libexec", filter=None, recursive=False, vars=vars) copydir("{qt_prefix_dir}/resources", - "{pyside_package_dir}/PySide2/Qt/resources", + "{st_build_dir}/{st_package_name}/Qt/resources", filter=None, recursive=False, vars=vars) - # <qt>/plugins/* -> <setup>/PySide2/Qt/plugins - copydir("{qt_plugins_dir}", - "{pyside_package_dir}/PySide2/Qt/plugins", - filter=["*.so"], - recursive=True, - vars=vars) - - # <qt>/qml/* -> <setup>/PySide2/Qt/qml - copydir("{qt_qml_dir}", - "{pyside_package_dir}/PySide2/Qt/qml", - filter=None, - force=False, - recursive=True, - ignore=["*.so.debug"], - vars=vars) - - # <qt>/translations/* -> <setup>/PySide2/Qt/translations - - copydir("{qt_translations_dir}", - "{pyside_package_dir}/PySide2/Qt/translations", - filter=["*.qm", "*.pak"], - force=False, - vars=vars) - - # Copy the qt.conf file to libexec. - copyfile( - "{build_dir}/pyside2/PySide2/qt.conf", - "{pyside_package_dir}/PySide2/Qt/libexec", - vars=vars) + if copy_plugins: + # <qt>/plugins/* -> <setup>/{st_package_name}/Qt/plugins + copydir("{qt_plugins_dir}", + "{st_build_dir}/{st_package_name}/Qt/plugins", + filter=["*.so"], + recursive=True, + vars=vars) + + if copy_qml: + # <qt>/qml/* -> <setup>/{st_package_name}/Qt/qml + copydir("{qt_qml_dir}", + "{st_build_dir}/{st_package_name}/Qt/qml", + filter=None, + force=False, + recursive=True, + ignore=["*.so.debug"], + vars=vars) + + if copy_translations: + # <qt>/translations/* -> + # <setup>/{st_package_name}/Qt/translations + copydir("{qt_translations_dir}", + "{st_build_dir}/{st_package_name}/Qt/translations", + filter=["*.qm", "*.pak"], + force=False, + vars=vars) + + if copy_qt_conf: + # Copy the qt.conf file to libexec. + copyfile( + "{build_dir}/pyside2/{st_package_name}/qt.conf", + "{st_build_dir}/{st_package_name}/Qt/libexec", + vars=vars) diff --git a/build_scripts/platforms/macos.py b/build_scripts/platforms/macos.py index 936f4ca90..49f02754d 100644 --- a/build_scripts/platforms/macos.py +++ b/build_scripts/platforms/macos.py @@ -37,12 +37,29 @@ ## ############################################################################# -import fnmatch, os +import fnmatch +import os from ..utils import copydir, copyfile, macos_fix_rpaths_for_library +from ..config import config -def prepare_standalone_package_macos(self, executables, vars): + +def prepare_standalone_package_macos(self, vars): built_modules = vars['built_modules'] + constrain_modules = None + copy_plugins = True + copy_qml = True + copy_translations = True + copy_qt_conf = True + + if config.is_internal_shiboken_generator_build(): + constrain_modules = ["Core", "Network", "Xml", "XmlPatterns"] + constrain_frameworks = ['Qt' + name + '.framework' for name in constrain_modules] + copy_plugins = False + copy_qml = False + copy_translations = False + copy_qt_conf = False + # Directory filter for skipping unnecessary files. def general_dir_filter(dir_name, parent_full_path, dir_full_path): if fnmatch.fnmatch(dir_name, "*.dSYM"): @@ -52,6 +69,7 @@ def prepare_standalone_package_macos(self, executables, vars): # Filter out debug plugins and qml plugins in the # debug_and_release config. no_copy_debug = True + def file_variant_filter(file_name, file_full_path): if self.qtinfo.build_type != 'debug_and_release': return True @@ -59,17 +77,16 @@ def prepare_standalone_package_macos(self, executables, vars): return False return True - # <qt>/lib/* -> <setup>/PySide2/Qt/lib + # <qt>/lib/* -> <setup>/{st_package_name}/Qt/lib if self.qt_is_framework_build(): - framework_built_modules = [ - 'Qt' + name + '.framework' for name in built_modules] - - def framework_dir_filter(dir_name, parent_full_path, - dir_full_path): + def framework_dir_filter(dir_name, parent_full_path, dir_full_path): if '.framework' in dir_name: - if dir_name.startswith('QtWebEngine') and not \ - self.is_webengine_built(built_modules): + if (dir_name.startswith('QtWebEngine') and + not self.is_webengine_built(built_modules)): + return False + if constrain_modules and dir_name not in constrain_frameworks: return False + if dir_name in ['Headers', 'fonts']: return False if dir_full_path.endswith('Versions/Current'): @@ -84,6 +101,7 @@ def prepare_standalone_package_macos(self, executables, vars): # Filter out debug frameworks in the # debug_and_release config. no_copy_debug = True + def framework_variant_filter(file_name, file_full_path): if self.qtinfo.build_type != 'debug_and_release': return True @@ -93,7 +111,7 @@ def prepare_standalone_package_macos(self, executables, vars): return False return True - copydir("{qt_lib_dir}", "{pyside_package_dir}/PySide2/Qt/lib", + copydir("{qt_lib_dir}", "{st_build_dir}/{st_package_name}/Qt/lib", recursive=True, vars=vars, ignore=["*.la", "*.a", "*.cmake", "*.pc", "*.prl"], dir_filter_function=framework_dir_filter, @@ -104,7 +122,7 @@ def prepare_standalone_package_macos(self, executables, vars): # from Versions/5/Helpers, thus adding two more levels of # directory hierarchy. if self.is_webengine_built(built_modules): - qt_lib_path = "{pyside_package_dir}/PySide2/Qt/lib".format( + qt_lib_path = "{st_build_dir}/{st_package_name}/Qt/lib".format( **vars) bundle = "QtWebEngineCore.framework/Helpers/" bundle += "QtWebEngineProcess.app" @@ -120,9 +138,11 @@ def prepare_standalone_package_macos(self, executables, vars): if 'WebKit' not in built_modules: ignored_modules.extend(['libQt5WebKit*.dylib']) accepted_modules = ['libQt5*.5.dylib'] + if constrain_modules: + accepted_modules = ["libQt5" + module + "*.5.dylib" for module in constrain_modules] copydir("{qt_lib_dir}", - "{pyside_package_dir}/PySide2/Qt/lib", + "{st_build_dir}/{st_package_name}/Qt/lib", filter=accepted_modules, ignore=ignored_modules, file_filter_function=file_variant_filter, @@ -130,53 +150,58 @@ def prepare_standalone_package_macos(self, executables, vars): if self.is_webengine_built(built_modules): copydir("{qt_lib_execs_dir}", - "{pyside_package_dir}/PySide2/Qt/libexec", + "{st_build_dir}/{st_package_name}/Qt/libexec", filter=None, recursive=False, vars=vars) copydir("{qt_prefix_dir}/resources", - "{pyside_package_dir}/PySide2/Qt/resources", + "{st_build_dir}/{st_package_name}/Qt/resources", filter=None, recursive=False, vars=vars) # Fix rpath for WebEngine process executable. - pyside_package_dir = vars['pyside_package_dir'] - qt_libexec_path = "{}/PySide2/Qt/libexec".format(pyside_package_dir) + qt_libexec_path = "{st_build_dir}/{st_package_name}/Qt/libexec".format(**vars) binary = "QtWebEngineProcess" final_path = os.path.join(qt_libexec_path, binary) rpath = "@loader_path/../lib" macos_fix_rpaths_for_library(final_path, rpath) - # Copy the qt.conf file to libexec. - copyfile( - "{build_dir}/pyside2/PySide2/qt.conf", - "{pyside_package_dir}/PySide2/Qt/libexec", - vars=vars) + if copy_qt_conf: + # Copy the qt.conf file to libexec. + copyfile( + "{build_dir}/pyside2/{st_package_name}/qt.conf", + "{st_build_dir}/{st_package_name}/Qt/libexec", + vars=vars) + + if copy_plugins: + # <qt>/plugins/* -> <setup>/{st_package_name}/Qt/plugins + copydir("{qt_plugins_dir}", + "{st_build_dir}/{st_package_name}/Qt/plugins", + filter=["*.dylib"], + recursive=True, + dir_filter_function=general_dir_filter, + file_filter_function=file_variant_filter, + vars=vars) + - # <qt>/plugins/* -> <setup>/PySide2/Qt/plugins - copydir("{qt_plugins_dir}", - "{pyside_package_dir}/PySide2/Qt/plugins", - filter=["*.dylib"], - recursive=True, - dir_filter_function=general_dir_filter, - file_filter_function=file_variant_filter, - vars=vars) - - # <qt>/qml/* -> <setup>/PySide2/Qt/qml - copydir("{qt_qml_dir}", - "{pyside_package_dir}/PySide2/Qt/qml", - filter=None, - recursive=True, - force=False, - dir_filter_function=general_dir_filter, - file_filter_function=file_variant_filter, - vars=vars) - - # <qt>/translations/* -> <setup>/PySide2/Qt/translations - copydir("{qt_translations_dir}", - "{pyside_package_dir}/PySide2/Qt/translations", - filter=["*.qm", "*.pak"], - force=False, - vars=vars) + if copy_qml: + # <qt>/qml/* -> <setup>/{st_package_name}/Qt/qml + copydir("{qt_qml_dir}", + "{st_build_dir}/{st_package_name}/Qt/qml", + filter=None, + recursive=True, + force=False, + dir_filter_function=general_dir_filter, + file_filter_function=file_variant_filter, + vars=vars) + + if copy_translations: + # <qt>/translations/* -> + # <setup>/{st_package_name}/Qt/translations + copydir("{qt_translations_dir}", + "{st_build_dir}/{st_package_name}/Qt/translations", + filter=["*.qm", "*.pak"], + force=False, + vars=vars) diff --git a/build_scripts/platforms/unix.py b/build_scripts/platforms/unix.py index e7fa92390..7dce11612 100644 --- a/build_scripts/platforms/unix.py +++ b/build_scripts/platforms/unix.py @@ -37,73 +37,31 @@ ## ############################################################################# -import os, re, sys +import os +import sys from .linux import prepare_standalone_package_linux from .macos import prepare_standalone_package_macos + +from ..config import config from ..options import * from ..utils import copydir, copyfile, rmtree, makefile from ..utils import regenerate_qt_resources + def prepare_packages_posix(self, vars): executables = [] - # <build>/shiboken2/doc/html/* -> - # <setup>/PySide2/docs/shiboken2 - copydir( - "{build_dir}/shiboken2/doc/html", - "{pyside_package_dir}/PySide2/docs/shiboken2", - force=False, vars=vars) - # <install>/lib/site-packages/PySide2/* -> <setup>/PySide2 - copydir( - "{site_packages_dir}/PySide2", - "{pyside_package_dir}/PySide2", - vars=vars) - # <install>/lib/site-packages/shiboken2.so -> - # <setup>/PySide2/shiboken2.so - shiboken_module_name = 'shiboken2.so' - shiboken_src_path = "{site_packages_dir}".format(**vars) - maybe_shiboken_names = [f for f in os.listdir(shiboken_src_path) - if re.match(r'shiboken.*\.so', f)] - if maybe_shiboken_names: - shiboken_module_name = maybe_shiboken_names[0] - vars.update({'shiboken_module_name': shiboken_module_name}) - copyfile( - "{site_packages_dir}/{shiboken_module_name}", - "{pyside_package_dir}/PySide2/{shiboken_module_name}", - vars=vars) - # <install>/lib/site-packages/pyside2uic/* -> - # <setup>/pyside2uic + + # <install>/lib/site-packages/{st_package_name}/* -> + # <setup>/{st_package_name} + # This copies the module .so/.dylib files and various .py files + # (__init__, config, git version, etc.) copydir( - "{site_packages_dir}/pyside2uic", - "{pyside_package_dir}/pyside2uic", - force=False, vars=vars) - if sys.version_info[0] > 2: - rmtree("{pyside_package_dir}/pyside2uic/port_v2".format(**vars)) - else: - rmtree("{pyside_package_dir}/pyside2uic/port_v3".format(**vars)) - # <install>/bin/pyside2-uic -> PySide2/scripts/uic.py - makefile( - "{pyside_package_dir}/PySide2/scripts/__init__.py", + "{site_packages_dir}/{st_package_name}", + "{st_build_dir}/{st_package_name}", vars=vars) - copyfile( - "{install_dir}/bin/pyside2-uic", - "{pyside_package_dir}/PySide2/scripts/uic.py", - force=False, vars=vars) - copyfile( - "{install_dir}/bin/pyside_tool.py", - "{pyside_package_dir}/PySide2/scripts/pyside_tool.py", - force=False, vars=vars) - # <install>/bin/* -> PySide2/ - executables.extend(copydir( - "{install_dir}/bin/", - "{pyside_package_dir}/PySide2", - filter=[ - "pyside2-lupdate", - "pyside2-rcc", - "shiboken2", - ], - recursive=False, vars=vars)) - # <install>/lib/lib* -> PySide2/ - config = self.get_built_pyside_config(vars) + + generated_config = self.get_built_pyside_config(vars) + def adjusted_lib_name(name, version): postfix = '' if sys.platform.startswith('linux'): @@ -111,61 +69,144 @@ def prepare_packages_posix(self, vars): elif sys.platform == 'darwin': postfix = '.' + version + '.dylib' return name + postfix - copydir( - "{install_dir}/lib/", - "{pyside_package_dir}/PySide2", - filter=[ - adjusted_lib_name("libpyside*", - config['pyside_library_soversion']), - adjusted_lib_name("libshiboken*", - config['shiboken_library_soversion']), - ], - recursive=False, vars=vars, force_copy_symlinks=True) - # <install>/share/PySide2/typesystems/* -> - # <setup>/PySide2/typesystems - copydir( - "{install_dir}/share/PySide2/typesystems", - "{pyside_package_dir}/PySide2/typesystems", - vars=vars) - # <install>/include/* -> <setup>/PySide2/include - copydir( - "{install_dir}/include", - "{pyside_package_dir}/PySide2/include", - vars=vars) - # <source>/pyside2/PySide2/support/* -> - # <setup>/PySide2/support/* - copydir( - "{build_dir}/pyside2/PySide2/support", - "{pyside_package_dir}/PySide2/support", - vars=vars) - if not OPTION_NOEXAMPLES: - # examples/* -> <setup>/PySide2/examples - copydir(os.path.join(self.script_dir, "examples"), - "{pyside_package_dir}/PySide2/examples", - force=False, vars=vars) - # Re-generate examples Qt resource files for Python 3 - # compatibility - if sys.version_info[0] == 3: - examples_path = "{pyside_package_dir}/PySide2/examples".format( - **vars) - pyside_rcc_path = "{install_dir}/bin/pyside2-rcc".format( - **vars) - pyside_rcc_options = '-py3' - regenerate_qt_resources(examples_path, pyside_rcc_path, - pyside_rcc_options) + + if config.is_internal_shiboken_module_build(): + # <build>/shiboken2/doc/html/* -> + # <setup>/{st_package_name}/docs/shiboken2 + copydir( + "{build_dir}/shiboken2/doc/html", + "{st_build_dir}/{st_package_name}/docs/shiboken2", + force=False, vars=vars) + + # <install>/lib/lib* -> {st_package_name}/ + copydir( + "{install_dir}/lib/", + "{st_build_dir}/{st_package_name}", + filter=[ + adjusted_lib_name("libshiboken*", + generated_config['shiboken_library_soversion']), + ], + recursive=False, vars=vars, force_copy_symlinks=True) + + if config.is_internal_shiboken_generator_build(): + # <install>/bin/* -> {st_package_name}/ + executables.extend(copydir( + "{install_dir}/bin/", + "{st_build_dir}/{st_package_name}", + filter=[ + "shiboken2", + ], + recursive=False, vars=vars)) + + # Used to create scripts directory. + makefile( + "{st_build_dir}/{st_package_name}/scripts/shiboken_tool.py", + vars=vars) + + # For setting up setuptools entry points. + copyfile( + "{install_dir}/bin/shiboken_tool.py", + "{st_build_dir}/{st_package_name}/scripts/shiboken_tool.py", + force=False, vars=vars) + + if config.is_internal_shiboken_generator_build() or config.is_internal_pyside_build(): + # <install>/include/* -> <setup>/{st_package_name}/include + copydir( + "{install_dir}/include/{cmake_package_name}", + "{st_build_dir}/{st_package_name}/include", + vars=vars) + + if config.is_internal_pyside_build(): + # <install>/lib/site-packages/pyside2uic/* -> + # <setup>/pyside2uic + copydir( + "{site_packages_dir}/pyside2uic", + "{st_build_dir}/pyside2uic", + force=False, vars=vars) + if sys.version_info[0] > 2: + rmtree("{st_build_dir}/pyside2uic/port_v2".format(**vars)) + else: + rmtree("{st_build_dir}/pyside2uic/port_v3".format(**vars)) + + # <install>/bin/pyside2-uic -> {st_package_name}/scripts/uic.py + makefile( + "{st_build_dir}/{st_package_name}/scripts/__init__.py", + vars=vars) + copyfile( + "{install_dir}/bin/pyside2-uic", + "{st_build_dir}/{st_package_name}/scripts/uic.py", + force=False, vars=vars) + + # For setting up setuptools entry points + copyfile( + "{install_dir}/bin/pyside_tool.py", + "{st_build_dir}/{st_package_name}/scripts/pyside_tool.py", + force=False, vars=vars) + + # <install>/bin/* -> {st_package_name}/ + executables.extend(copydir( + "{install_dir}/bin/", + "{st_build_dir}/{st_package_name}", + filter=[ + "pyside2-lupdate", + "pyside2-rcc", + ], + recursive=False, vars=vars)) + + # <install>/lib/lib* -> {st_package_name}/ + copydir( + "{install_dir}/lib/", + "{st_build_dir}/{st_package_name}", + filter=[ + adjusted_lib_name("libpyside*", + generated_config['pyside_library_soversion']), + ], + recursive=False, vars=vars, force_copy_symlinks=True) + + # <install>/share/{st_package_name}/typesystems/* -> + # <setup>/{st_package_name}/typesystems + copydir( + "{install_dir}/share/{st_package_name}/typesystems", + "{st_build_dir}/{st_package_name}/typesystems", + vars=vars) + + # <source>/pyside2/{st_package_name}/support/* -> + # <setup>/{st_package_name}/support/* + copydir( + "{build_dir}/pyside2/{st_package_name}/support", + "{st_build_dir}/{st_package_name}/support", + vars=vars) + + if not OPTION_NOEXAMPLES: + # examples/* -> <setup>/{st_package_name}/examples + copydir(os.path.join(self.script_dir, "examples"), + "{st_build_dir}/{st_package_name}/examples", + force=False, vars=vars) + # Re-generate examples Qt resource files for Python 3 + # compatibility + if sys.version_info[0] == 3: + examples_path = "{st_build_dir}/{st_package_name}/examples".format( + **vars) + pyside_rcc_path = "{install_dir}/bin/pyside2-rcc".format( + **vars) + pyside_rcc_options = '-py3' + regenerate_qt_resources(examples_path, pyside_rcc_path, + pyside_rcc_options) + # Copy Qt libs to package if OPTION_STANDALONE: - vars['built_modules'] = config['built_modules'] - if sys.platform == 'darwin': - prepare_standalone_package_macos(self, executables, vars) - else: - prepare_standalone_package_linux(self, executables, vars) + if config.is_internal_pyside_build() or config.is_internal_shiboken_generator_build(): + vars['built_modules'] = generated_config['built_modules'] + if sys.platform == 'darwin': + prepare_standalone_package_macos(self, vars) + else: + prepare_standalone_package_linux(self, vars) - # Copy over clang before rpath patching. - self.prepare_standalone_clang(is_win=False) + if config.is_internal_shiboken_generator_build(): + # Copy over clang before rpath patching. + self.prepare_standalone_clang(is_win=False) # Update rpath to $ORIGIN - if (sys.platform.startswith('linux') or - sys.platform.startswith('darwin')): - self.update_rpath("{pyside_package_dir}/PySide2".format(**vars), - executables) + if sys.platform.startswith('linux') or sys.platform.startswith('darwin'): + rpath_path = "{st_build_dir}/{st_package_name}".format(**vars) + self.update_rpath(rpath_path, executables) diff --git a/build_scripts/platforms/windows_desktop.py b/build_scripts/platforms/windows_desktop.py index aa9487ec6..6307238a1 100644 --- a/build_scripts/platforms/windows_desktop.py +++ b/build_scripts/platforms/windows_desktop.py @@ -38,153 +38,225 @@ ############################################################################# import functools -import os, re, sys +import os +import sys + +from ..config import config from ..options import * from ..utils import copydir, copyfile, rmtree, makefile from ..utils import regenerate_qt_resources, filter_match from ..utils import download_and_extract_7z + def prepare_packages_win32(self, vars): # For now, debug symbols will not be shipped into the package. copy_pdbs = False pdbs = [] if (self.debug or self.build_type == 'RelWithDebInfo') and copy_pdbs: pdbs = ['*.pdb'] - # <install>/lib/site-packages/PySide2/* -> <setup>/PySide2 + + # <install>/lib/site-packages/{st_package_name}/* -> + # <setup>/{st_package_name} + # This copies the module .pyd files and various .py files + # (__init__, config, git version, etc.) copydir( - "{site_packages_dir}/PySide2", - "{pyside_package_dir}/PySide2", + "{site_packages_dir}/{st_package_name}", + "{st_build_dir}/{st_package_name}", vars=vars) - built_modules = self.get_built_pyside_config(vars)['built_modules'] - # <build>/pyside2/PySide2/*.pdb -> <setup>/PySide2 - copydir( - "{build_dir}/pyside2/PySide2", - "{pyside_package_dir}/PySide2", - filter=pdbs, - recursive=False, vars=vars) + if config.is_internal_shiboken_module_build(): + # <build>/shiboken2/doc/html/* -> + # <setup>/{st_package_name}/docs/shiboken2 + copydir( + "{build_dir}/shiboken2/doc/html", + "{st_build_dir}/{st_package_name}/docs/shiboken2", + force=False, vars=vars) + + # <install>/bin/*.dll -> {st_package_name}/ + copydir( + "{install_dir}/bin/", + "{st_build_dir}/{st_package_name}", + filter=["shiboken*.dll"], + recursive=False, vars=vars) - # <build>/shiboken2/doc/html/* -> - # <setup>/PySide2/docs/shiboken2 - copydir( - "{build_dir}/shiboken2/doc/html", - "{pyside_package_dir}/PySide2/docs/shiboken2", - force=False, vars=vars) - - # <install>/lib/site-packages/shiboken2.pyd -> - # <setup>/PySide2/shiboken2.pyd - shiboken_module_name = 'shiboken2.pyd' - shiboken_src_path = "{site_packages_dir}".format(**vars) - maybe_shiboken_names = [f for f in os.listdir(shiboken_src_path) - if re.match(r'shiboken.*\.pyd', f)] - if maybe_shiboken_names: - shiboken_module_name = maybe_shiboken_names[0] - vars.update({'shiboken_module_name': shiboken_module_name}) - copyfile( - "{site_packages_dir}/{shiboken_module_name}", - "{pyside_package_dir}/PySide2/{shiboken_module_name}", - vars=vars) - # @TODO: Fix this .pdb file not to overwrite release - # {shibokengenerator}.pdb file. - # Task-number: PYSIDE-615 - copydir( - "{build_dir}/shiboken2/shibokenmodule", - "{pyside_package_dir}/PySide2", - filter=pdbs, - recursive=False, vars=vars) + # <install>/lib/*.lib -> {st_package_name}/ + copydir( + "{install_dir}/lib/", + "{st_build_dir}/{st_package_name}", + filter=["shiboken*.lib"], + recursive=False, vars=vars) - # <install>/lib/site-packages/pyside2uic/* -> - # <setup>/pyside2uic - copydir( - "{site_packages_dir}/pyside2uic", - "{pyside_package_dir}/pyside2uic", - force=False, vars=vars) - if sys.version_info[0] > 2: - rmtree("{pyside_package_dir}/pyside2uic/port_v2".format(**vars)) - else: - rmtree("{pyside_package_dir}/pyside2uic/port_v3".format(**vars)) + # @TODO: Fix this .pdb file not to overwrite release + # {shibokengenerator}.pdb file. + # Task-number: PYSIDE-615 + copydir( + "{build_dir}/shiboken2/shibokenmodule", + "{st_build_dir}/{st_package_name}", + filter=pdbs, + recursive=False, vars=vars) - # <install>/bin/pyside2-uic -> PySide2/scripts/uic.py - makefile( - "{pyside_package_dir}/PySide2/scripts/__init__.py", - vars=vars) - copyfile( - "{install_dir}/bin/pyside2-uic", - "{pyside_package_dir}/PySide2/scripts/uic.py", - force=False, vars=vars) - - # For setting up entry points - copyfile( - "{install_dir}/bin/pyside_tool.py", - "{pyside_package_dir}/PySide2/scripts/pyside_tool.py", - force=False, vars=vars) - - # <install>/bin/*.exe,*.dll,*.pdb -> PySide2/ - copydir( - "{install_dir}/bin/", - "{pyside_package_dir}/PySide2", - filter=["*.exe", "*.dll"], - recursive=False, vars=vars) - # @TODO: Fix this .pdb file not to overwrite release - # {shibokenmodule}.pdb file. - # Task-number: PYSIDE-615 - copydir( - "{build_dir}/shiboken2/generator", - "{pyside_package_dir}/PySide2", - filter=pdbs, - recursive=False, vars=vars) + # pdb files for libshiboken and libpyside + copydir( + "{build_dir}/shiboken2/libshiboken", + "{st_build_dir}/{st_package_name}", + filter=pdbs, + recursive=False, vars=vars) - # <install>/lib/*.lib -> PySide2/ - copydir( - "{install_dir}/lib/", - "{pyside_package_dir}/PySide2", - filter=["*.lib"], - recursive=False, vars=vars) + if config.is_internal_shiboken_generator_build(): + # <install>/bin/*.dll -> {st_package_name}/ + copydir( + "{install_dir}/bin/", + "{st_build_dir}/{st_package_name}", + filter=["shiboken*.exe"], + recursive=False, vars=vars) - # <install>/share/PySide2/typesystems/* -> - # <setup>/PySide2/typesystems - copydir( - "{install_dir}/share/PySide2/typesystems", - "{pyside_package_dir}/PySide2/typesystems", - vars=vars) + # Used to create scripts directory. + makefile( + "{st_build_dir}/{st_package_name}/scripts/shiboken_tool.py", + vars=vars) - # <install>/include/* -> <setup>/PySide2/include - copydir( - "{install_dir}/include", - "{pyside_package_dir}/PySide2/include", - vars=vars) + # For setting up setuptools entry points. + copyfile( + "{install_dir}/bin/shiboken_tool.py", + "{st_build_dir}/{st_package_name}/scripts/shiboken_tool.py", + force=False, vars=vars) + + # @TODO: Fix this .pdb file not to overwrite release + # {shibokenmodule}.pdb file. + # Task-number: PYSIDE-615 + copydir( + "{build_dir}/shiboken2/generator", + "{st_build_dir}/{st_package_name}", + filter=pdbs, + recursive=False, vars=vars) - # <source>/pyside2/PySide2/support/* -> - # <setup>/PySide2/support/* - copydir( - "{build_dir}/pyside2/PySide2/support", - "{pyside_package_dir}/PySide2/support", - vars=vars) + if config.is_internal_shiboken_generator_build() or config.is_internal_pyside_build(): + # <install>/include/* -> <setup>/{st_package_name}/include + copydir( + "{install_dir}/include/{cmake_package_name}", + "{st_build_dir}/{st_package_name}/include", + vars=vars) + + if config.is_internal_pyside_build(): + # <build>/pyside2/{st_package_name}/*.pdb -> + # <setup>/{st_package_name} + copydir( + "{build_dir}/pyside2/{st_package_name}", + "{st_build_dir}/{st_package_name}", + filter=pdbs, + recursive=False, vars=vars) + + # <install>/lib/site-packages/pyside2uic/* -> + # <setup>/pyside2uic + copydir( + "{site_packages_dir}/pyside2uic", + "{st_build_dir}/pyside2uic", + force=False, vars=vars) + if sys.version_info[0] > 2: + rmtree("{st_build_dir}/pyside2uic/port_v2".format(**vars)) + else: + rmtree("{st_build_dir}/pyside2uic/port_v3".format(**vars)) + + # <install>/bin/pyside2-uic -> {st_package_name}/scripts/uic.py + makefile( + "{st_build_dir}/{st_package_name}/scripts/__init__.py", + vars=vars) + copyfile( + "{install_dir}/bin/pyside2-uic", + "{st_build_dir}/{st_package_name}/scripts/uic.py", + force=False, vars=vars) + + # For setting up setuptools entry points + copyfile( + "{install_dir}/bin/pyside_tool.py", + "{st_build_dir}/{st_package_name}/scripts/pyside_tool.py", + force=False, vars=vars) + + # <install>/bin/*.exe,*.dll -> {st_package_name}/ + copydir( + "{install_dir}/bin/", + "{st_build_dir}/{st_package_name}", + filter=["pyside*.exe", "pyside*.dll"], + recursive=False, vars=vars) + + # <install>/lib/*.lib -> {st_package_name}/ + copydir( + "{install_dir}/lib/", + "{st_build_dir}/{st_package_name}", + filter=["pyside*.lib"], + recursive=False, vars=vars) + + # <install>/share/{st_package_name}/typesystems/* -> + # <setup>/{st_package_name}/typesystems + copydir( + "{install_dir}/share/{st_package_name}/typesystems", + "{st_build_dir}/{st_package_name}/typesystems", + vars=vars) + + # <source>/pyside2/{st_package_name}/support/* -> + # <setup>/{st_package_name}/support/* + copydir( + "{build_dir}/pyside2/{st_package_name}/support", + "{st_build_dir}/{st_package_name}/support", + vars=vars) + + copydir( + "{build_dir}/pyside2/libpyside", + "{st_build_dir}/{st_package_name}", + filter=pdbs, + recursive=False, vars=vars) - if not OPTION_NOEXAMPLES: - # examples/* -> <setup>/PySide2/examples - copydir(os.path.join(self.script_dir, "examples"), - "{pyside_package_dir}/PySide2/examples", + if not OPTION_NOEXAMPLES: + # examples/* -> <setup>/{st_package_name}/examples + copydir(os.path.join(self.script_dir, "examples"), + "{st_build_dir}/{st_package_name}/examples", + force=False, vars=vars) + # Re-generate examples Qt resource files for Python 3 + # compatibility + if sys.version_info[0] == 3: + examples_path = "{st_build_dir}/{st_package_name}/examples".format( + **vars) + pyside_rcc_path = "{install_dir}/bin/pyside2-rcc".format( + **vars) + pyside_rcc_options = '-py3' + regenerate_qt_resources(examples_path, pyside_rcc_path, + pyside_rcc_options) + + if vars['ssl_libs_dir']: + # <ssl_libs>/* -> <setup>/{st_package_name}/openssl + copydir("{ssl_libs_dir}", "{st_build_dir}/{st_package_name}/openssl", + filter=[ + "libeay32.dll", + "ssleay32.dll"], force=False, vars=vars) - # Re-generate examples Qt resource files for Python 3 - # compatibility - if sys.version_info[0] == 3: - examples_path = "{pyside_package_dir}/PySide2/examples".format( - **vars) - pyside_rcc_path = "{install_dir}/bin/pyside2-rcc".format( - **vars) - pyside_rcc_options = '-py3' - regenerate_qt_resources(examples_path, pyside_rcc_path, - pyside_rcc_options) - - # <ssl_libs>/* -> <setup>/PySide2/openssl - copydir("{ssl_libs_dir}", "{pyside_package_dir}/PySide2/openssl", - filter=[ - "libeay32.dll", - "ssleay32.dll"], - force=False, vars=vars) - - # <qt>/bin/*.dll and Qt *.exe -> <setup>/PySide2 + + if config.is_internal_pyside_build() or config.is_internal_shiboken_generator_build(): + copy_qt_artifacts(self, copy_pdbs, vars) + + +def copy_qt_artifacts(self, copy_pdbs, vars): + built_modules = self.get_built_pyside_config(vars)['built_modules'] + + constrain_modules = None + copy_plugins = True + copy_qml = True + copy_translations = True + copy_qt_conf = True + copy_qt_permanent_artifacts = True + copy_msvc_redist = False + copy_clang = False + + if config.is_internal_shiboken_generator_build(): + constrain_modules = ["Core", "Network", "Xml", "XmlPatterns"] + copy_plugins = False + copy_qml = False + copy_translations = False + copy_qt_conf = False + copy_qt_permanent_artifacts = False + copy_msvc_redist = True + copy_clang = True + + # <qt>/bin/*.dll and Qt *.exe -> <setup>/{st_package_name} qt_artifacts_permanent = [ "opengl*.dll", "d3d*.dll", @@ -195,6 +267,7 @@ def prepare_packages_win32(self, vars): "lconvert.exe", "qtdiag.exe" ] + # MSVC redistributable msvc_redist = [ "concrt140.dll", @@ -218,8 +291,14 @@ def prepare_packages_win32(self, vars): else: egl_suffix = '' qt_artifacts_egl = [a.format(egl_suffix) for a in qt_artifacts_egl] - qt_artifacts_permanent += qt_artifacts_egl - qt_artifacts_permanent += msvc_redist + + artifacts = [] + if copy_qt_permanent_artifacts: + artifacts += qt_artifacts_permanent + artifacts += qt_artifacts_egl + + if copy_msvc_redist: + artifacts += msvc_redist # Extract Qt dependency dll's when building on Qt CI # There is no proper CI env variable, so using agent launch params @@ -231,15 +310,22 @@ def prepare_packages_win32(self, vars): zip_file = "pyside_qt_deps_32.7z" download_and_extract_7z(redist_url + zip_file, "{qt_bin_dir}".format(**vars)) - copydir("{qt_bin_dir}", "{pyside_package_dir}/PySide2", - filter=qt_artifacts_permanent, - recursive=False, vars=vars) + if artifacts: + copydir("{qt_bin_dir}", + "{st_build_dir}/{st_package_name}", + filter=artifacts, recursive=False, vars=vars) - # <qt>/bin/*.dll and Qt *.pdbs -> <setup>/PySide2 part two + # <qt>/bin/*.dll and Qt *.pdbs -> <setup>/{st_package_name} part two # File filter to copy only debug or only release files. - qt_dll_patterns = ["Qt5*{}.dll", "lib*{}.dll"] - if copy_pdbs: - qt_dll_patterns += ["Qt5*{}.pdb", "lib*{}.pdb"] + if constrain_modules: + qt_dll_patterns = ["Qt5" + x + "{}.dll" for x in constrain_modules] + if copy_pdbs: + qt_dll_patterns += ["Qt5" + x + "{}.pdb" for x in constrain_modules] + else: + qt_dll_patterns = ["Qt5*{}.dll", "lib*{}.dll"] + if copy_pdbs: + qt_dll_patterns += ["Qt5*{}.pdb", "lib*{}.pdb"] + def qt_build_config_filter(patterns, file_name, file_full_path): release = [a.format('') for a in patterns] debug = [a.format('d') for a in patterns] @@ -289,56 +375,60 @@ def prepare_packages_win32(self, vars): return False qt_dll_filter = functools.partial(qt_build_config_filter, - qt_dll_patterns) - copydir("{qt_bin_dir}", "{pyside_package_dir}/PySide2", - file_filter_function=qt_dll_filter, - recursive=False, vars=vars) - - # <qt>/plugins/* -> <setup>/PySide2/plugins - plugin_dll_patterns = ["*{}.dll"] - pdb_pattern = "*{}.pdb" - if copy_pdbs: - plugin_dll_patterns += [pdb_pattern] - plugin_dll_filter = functools.partial(qt_build_config_filter, - plugin_dll_patterns) - copydir("{qt_plugins_dir}", "{pyside_package_dir}/PySide2/plugins", - file_filter_function=plugin_dll_filter, - vars=vars) + qt_dll_patterns) + copydir("{qt_bin_dir}", + "{st_build_dir}/{st_package_name}", + file_filter_function=qt_dll_filter, + recursive=False, vars=vars) - # <qt>/translations/* -> <setup>/PySide2/translations - copydir("{qt_translations_dir}", - "{pyside_package_dir}/PySide2/translations", - filter=["*.qm", "*.pak"], - force=False, - vars=vars) + if copy_plugins: + # <qt>/plugins/* -> <setup>/{st_package_name}/plugins + plugin_dll_patterns = ["*{}.dll"] + pdb_pattern = "*{}.pdb" + if copy_pdbs: + plugin_dll_patterns += [pdb_pattern] + plugin_dll_filter = functools.partial(qt_build_config_filter, + plugin_dll_patterns) + copydir("{qt_plugins_dir}", "{st_build_dir}/{st_package_name}/plugins", + file_filter_function=plugin_dll_filter, + vars=vars) - # <qt>/qml/* -> <setup>/PySide2/qml - qml_dll_patterns = ["*{}.dll"] - qml_ignore_patterns = qml_dll_patterns + [pdb_pattern] - qml_ignore = [a.format('') for a in qml_ignore_patterns] + if copy_translations: + # <qt>/translations/* -> <setup>/{st_package_name}/translations + copydir("{qt_translations_dir}", + "{st_build_dir}/{st_package_name}/translations", + filter=["*.qm", "*.pak"], + force=False, + vars=vars) - # Copy all files that are not dlls and pdbs (.qml, qmldir). - copydir("{qt_qml_dir}", "{pyside_package_dir}/PySide2/qml", - ignore=qml_ignore, - force=False, - recursive=True, - vars=vars) + if copy_qml: + # <qt>/qml/* -> <setup>/{st_package_name}/qml + qml_dll_patterns = ["*{}.dll"] + qml_ignore_patterns = qml_dll_patterns + [pdb_pattern] + qml_ignore = [a.format('') for a in qml_ignore_patterns] + + # Copy all files that are not dlls and pdbs (.qml, qmldir). + copydir("{qt_qml_dir}", "{st_build_dir}/{st_package_name}/qml", + ignore=qml_ignore, + force=False, + recursive=True, + vars=vars) - if copy_pdbs: - qml_dll_patterns += [pdb_pattern] - qml_dll_filter = functools.partial(qt_build_config_filter, - qml_dll_patterns) + if copy_pdbs: + qml_dll_patterns += [pdb_pattern] + qml_dll_filter = functools.partial(qt_build_config_filter, + qml_dll_patterns) - # Copy all dlls (and possibly pdbs). - copydir("{qt_qml_dir}", "{pyside_package_dir}/PySide2/qml", - file_filter_function=qml_dll_filter, - force=False, - recursive=True, - vars=vars) + # Copy all dlls (and possibly pdbs). + copydir("{qt_qml_dir}", "{st_build_dir}/{st_package_name}/qml", + file_filter_function=qml_dll_filter, + force=False, + recursive=True, + vars=vars) if self.is_webengine_built(built_modules): copydir("{qt_prefix_dir}/resources", - "{pyside_package_dir}/PySide2/resources", + "{st_build_dir}/{st_package_name}/resources", filter=None, recursive=False, vars=vars) @@ -346,26 +436,16 @@ def prepare_packages_win32(self, vars): filter = 'QtWebEngineProcess{}.exe'.format( 'd' if self.debug else '') copydir("{qt_bin_dir}", - "{pyside_package_dir}/PySide2", + "{st_build_dir}/{st_package_name}", filter=[filter], recursive=False, vars=vars) - # Copy the qt.conf file to prefix dir. - copyfile( - "{build_dir}/pyside2/PySide2/qt.conf", - "{pyside_package_dir}/PySide2", - vars=vars) - - self.prepare_standalone_clang(is_win=True) + if copy_qt_conf: + # Copy the qt.conf file to prefix dir. + copyfile( + "{build_dir}/pyside2/{st_package_name}/qt.conf", + "{st_build_dir}/{st_package_name}", + vars=vars) - # pdb files for libshiboken and libpyside - copydir( - "{build_dir}/shiboken2/libshiboken", - "{pyside_package_dir}/PySide2", - filter=pdbs, - recursive=False, vars=vars) - copydir( - "{build_dir}/pyside2/libpyside", - "{pyside_package_dir}/PySide2", - filter=pdbs, - recursive=False, vars=vars) + if copy_clang: + self.prepare_standalone_clang(is_win=True) diff --git a/build_scripts/setup_runner.py b/build_scripts/setup_runner.py new file mode 100644 index 000000000..a1526793e --- /dev/null +++ b/build_scripts/setup_runner.py @@ -0,0 +1,165 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys, os, textwrap + +from build_scripts.config import config +from build_scripts.main import get_package_version, get_setuptools_extension_modules +from build_scripts.main import cmd_class_dict +from build_scripts.options import OPTION_BUILD_TYPE, OPTION_INTERNAL_BUILD_TYPE +from build_scripts.utils import run_process + +from setuptools import setup + + +class SetupRunner(object): + def __init__(self, orig_argv): + self.invocations_list = [] + + # Keep the original args around in case we ever need to pass + # modified arguments to the sub invocations. + self.orig_argv = orig_argv + self.sub_argv = list(orig_argv) + + self.setup_script_dir = os.getcwd() + + @staticmethod + def cmd_line_argument_is_in_args(argument, args): + """ Check if command line argument was passed in args. """ + return any(arg for arg in list(args) if "--" + argument in arg) + + @staticmethod + def remove_cmd_line_argument_in_args(argument, args): + """ Remove command line argument from args. """ + return [arg for arg in list(args) if "--" + argument not in arg] + + @staticmethod + def construct_cmd_line_argument(name, value=None): + """ Constructs a command line argument given name and value. """ + if not value: + return "--{}".format(name) + return "--{}={}".format(name, value) + + @staticmethod + def construct_internal_build_type_cmd_line_argument(internal_build_type): + return SetupRunner.construct_cmd_line_argument("internal-build-type", internal_build_type) + + def add_setup_internal_invocation(self, build_type, reuse_build=False): + """ Enqueues a script sub-invocation to be executed later. """ + internal_build_type_arg = self.construct_internal_build_type_cmd_line_argument(build_type) + setup_cmd = [sys.executable] + self.sub_argv + [internal_build_type_arg] + + # Add --reuse-build option if requested and not already present. + if reuse_build and not self.cmd_line_argument_is_in_args("reuse-build", self.sub_argv): + setup_cmd.append(self.construct_cmd_line_argument("reuse-build")) + self.invocations_list.append(setup_cmd) + + def run_setup(self): + """ + Decide what kind of build is requested and then execute it. + In the top-level invocation case, the script + will spawn setup.py again (possibly multiple times). + In the internal invocation case, the script + will run setuptools.setup(). + """ + + # Prepare initial config. + config.init_config(build_type=OPTION_BUILD_TYPE, + internal_build_type=OPTION_INTERNAL_BUILD_TYPE, + cmd_class_dict=cmd_class_dict, + package_version=get_package_version(), + ext_modules=get_setuptools_extension_modules(), + setup_script_dir=self.setup_script_dir) + + # This is an internal invocation of setup.py, so start actual + # build. + if config.is_internal_invocation(): + if config.internal_build_type not in config.get_allowed_internal_build_values(): + raise RuntimeError("Invalid '{}' option given to --internal-build-type. " + .format(config.internal_build_type)) + self.run_setuptools_setup() + return + + # This is a top-level invocation of setup.py, so figure out what + # modules we will build and depending on that, call setup.py + # multiple times with different arguments. + if config.build_type not in config.get_allowed_top_level_build_values(): + raise RuntimeError("Invalid '{}' option given to --build-type. " + .format(config.build_type)) + + # Build everything: shiboken2, shiboken2-generator and PySide2. + if config.is_top_level_build_all(): + self.add_setup_internal_invocation(config.shiboken_module_option_name) + + # Reuse the shiboken build for the generator package instead + # of rebuilding it again. + self.add_setup_internal_invocation(config.shiboken_generator_option_name, + reuse_build=True) + + self.add_setup_internal_invocation(config.pyside_option_name) + + elif config.is_top_level_build_shiboken_module(): + self.add_setup_internal_invocation(config.shiboken_module_option_name) + + elif config.is_top_level_build_shiboken_generator(): + self.add_setup_internal_invocation(config.shiboken_generator_option_name) + + elif config.is_top_level_build_pyside(): + self.add_setup_internal_invocation(config.pyside_option_name) + + for cmd in self.invocations_list: + cmd_as_string = " ".join(cmd) + print("\nRunning process: {}\n".format(cmd_as_string)) + exit_code = run_process(cmd) + if exit_code != 0: + msg = textwrap.dedent(""" + setup.py invocation failed with exit code: {}.\n\n + setup.py invocation was: {} + """).format(exit_code, cmd_as_string) + raise RuntimeError(msg) + + @staticmethod + def run_setuptools_setup(): + """ + Runs setuptools.setup() once in a single setup.py + sub-invocation. + """ + + kwargs = config.setup_kwargs + setup(**kwargs) diff --git a/build_scripts/utils.py b/build_scripts/utils.py index 7160630d1..741de73b9 100644 --- a/build_scripts/utils.py +++ b/build_scripts/utils.py @@ -39,17 +39,13 @@ import sys import os -import stat import re import stat import errno -import time import shutil import subprocess import fnmatch -import glob import itertools -import popenasync import glob # There is no urllib.request in Python2 @@ -58,11 +54,9 @@ try: except ImportError: import urllib -from distutils import log +import distutils.log as log from distutils.errors import DistutilsOptionError from distutils.errors import DistutilsSetupError -from distutils.spawn import spawn -from distutils.spawn import DistutilsExecError try: WindowsError @@ -70,32 +64,6 @@ except NameError: WindowsError = None -def has_option(name): - try: - sys.argv.remove("--{}".format(name)) - return True - except ValueError: - pass - return False - - -def option_value(name): - for index, option in enumerate(sys.argv): - if option == '--' + name: - if index+1 >= len(sys.argv): - raise DistutilsOptionError("The option {} requires a " - "value".format(option)) - value = sys.argv[index+1] - sys.argv[index:index+2] = [] - return value - if option.startswith('--' + name + '='): - value = option[len(name)+3:] - sys.argv[index:index+1] = [] - return value - env_val = os.getenv(name.upper().replace('-', '_')) - return env_val - - def filter_match(name, patterns): for pattern in patterns: if pattern is None: @@ -182,7 +150,6 @@ def find_vcdir(version): """ from distutils.msvc9compiler import VS_BASE from distutils.msvc9compiler import Reg - from distutils import log vsbase = VS_BASE % version try: productdir = Reg.get_value(r"{}\Setup\VC".format(vsbase), "productdir") @@ -416,13 +383,15 @@ def rmtree(dirname, ignore=False): os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777 func(path) else: - raise + raise IOError shutil.rmtree(dirname, ignore_errors=ignore, onerror=handle_remove_readonly) def run_process_output(args, initial_env=None): if initial_env is None: initial_env = os.environ - std_out = subprocess.Popen(args, env = initial_env, universal_newlines = 1, + std_out = subprocess.Popen(args, + env = initial_env, + universal_newlines = 1, stdout=subprocess.PIPE).stdout result = [] for raw_line in std_out.readlines(): @@ -431,53 +400,21 @@ def run_process_output(args, initial_env=None): return result def run_process(args, initial_env=None): - def _log(buffer, check_new_line=False): - ends_with_new_line = False - if buffer.endswith('\n'): - ends_with_new_line = True - if check_new_line and buffer.find('\n') == -1: - return buffer - lines = buffer.splitlines() - buffer = '' - if check_new_line and not ends_with_new_line: - buffer = lines[-1] - lines = lines[:-1] - for line in lines: - log.info(line.rstrip('\r')) - return buffer - _log("Running process in {0}: {1}".format(os.getcwd(), - " ".join([(" " in x and '"{0}"'.format(x) or x) for x in args]))) - - if sys.platform != "win32": - try: - spawn(args) - return 0 - except DistutilsExecError: - return -1 - - shell = False - if sys.platform == "win32": - shell = True + """ + Run process until completion and return the process exit code. + Prints both stdout and stderr to the console. + No output is captured. + """ + log.info("Running process in directory {0}: command {1}".format( + os.getcwd(), + " ".join([(" " in x and '"{0}"'.format(x) or x) for x in args])) + ) if initial_env is None: initial_env = os.environ - proc = popenasync.Popen(args, - stdin = subprocess.PIPE, - stdout = subprocess.PIPE, - stderr = subprocess.STDOUT, - universal_newlines = 1, - shell = shell, - env = initial_env) - - log_buffer = None; - while proc.poll() is None: - log_buffer = _log(proc.read_async(wait=0.1, e=0)) - if log_buffer: - _log(log_buffer) - - proc.wait() - return proc.returncode + exit_code = subprocess.call(args, stderr=subprocess.STDOUT, env=initial_env) + return exit_code def get_environment_from_batch_command(env_cmd, initial=None): @@ -665,6 +602,9 @@ def macos_get_rpaths(libpath): ctr += 3 return rpaths +def macos_add_rpath(rpath, library_path): + back_tick('install_name_tool -add_rpath {rpath} {library_path}'.format( + rpath=rpath, library_path=library_path)) def macos_fix_rpaths_for_library(library_path, qt_lib_dir): """ Adds required rpath load commands to given library. @@ -703,8 +643,7 @@ def macos_fix_rpaths_for_library(library_path, qt_lib_dir): break if needs_loader_path and "@loader_path" not in existing_rpath_commands: - back_tick('install_name_tool -add_rpath {rpath} {library_path}'.format( - rpath="@loader_path", library_path=library_path)) + macos_add_rpath("@loader_path", library_path) # If the library depends on a Qt library, add an rpath load comment # pointing to the Qt lib directory. @@ -738,8 +677,7 @@ def macos_add_qt_rpath(library_path, qt_lib_dir, break if needs_qt_rpath: - back_tick('install_name_tool -add_rpath {rpath} {library_path}'.format( - rpath=qt_lib_dir, library_path=library_path)) + macos_add_rpath(qt_lib_dir, library_path) # Find an executable specified by a glob pattern ('foo*') in the OS path def find_glob_in_path(pattern): @@ -996,6 +934,17 @@ def copy_icu_libs(patchelf, destination_lib_dir): new_rpaths_string = ":".join(rpaths) linux_set_rpaths(patchelf, qt_core_library_path, new_rpaths_string) + +def linux_run_read_elf(executable_path): + cmd = "readelf -d {}".format(executable_path) + (out, err, code) = back_tick(cmd, True) + if code != 0: + raise RuntimeError("Running `readelf -d {}` failed with error " + "output:\n {}. ".format(executable_path, err)) + lines = split_and_strip(out) + return lines + + def linux_set_rpaths(patchelf, executable_path, rpath_string): """ Patches the `executable_path` with a new rpath string. """ @@ -1005,18 +954,32 @@ def linux_set_rpaths(patchelf, executable_path, rpath_string): raise RuntimeError("Error patching rpath in {}".format( executable_path)) + +def linux_get_dependent_libraries(executable_path): + """ + Returns a list of libraries that executable_path depends on. + """ + + lines = linux_run_read_elf(executable_path) + pattern = re.compile(r"^.+?\(NEEDED\).+?\[(.+?)\]$") + + library_lines = [] + for line in lines: + match = pattern.search(line) + if match: + library_line = match.group(1) + library_lines.append(library_line) + + return library_lines + + def linux_get_rpaths(executable_path): """ Returns a list of run path values embedded in the executable or just an empty list. """ - cmd = "readelf -d {}".format(executable_path) - (out, err, code) = back_tick(cmd, True) - if code != 0: - raise RuntimeError("Running `readelf -d {}` failed with error " - "output:\n {}. ".format(executable_path, err)) - lines = split_and_strip(out) + lines = linux_run_read_elf(executable_path) pattern = re.compile(r"^.+?\(RUNPATH\).+?\[(.+?)\]$") rpath_line = None @@ -1033,6 +996,7 @@ def linux_get_rpaths(executable_path): return rpaths + def rpaths_has_origin(rpaths): """ Return True if the specified list of rpaths has an "$ORIGIN" value @@ -1048,6 +1012,39 @@ def rpaths_has_origin(rpaths): return True return False + +def linux_needs_qt_rpath(executable_path): + """ + Returns true if library_path depends on Qt libraries. + """ + + dependencies = linux_get_dependent_libraries(executable_path) + + # Check if any library dependencies are Qt libraries (hacky). + needs_qt_rpath = False + for dep in dependencies: + if 'Qt' in dep: + needs_qt_rpath = True + break + return needs_qt_rpath + + +def linux_fix_rpaths_for_library(patchelf, executable_path, qt_rpath, override=False): + """ + Adds or overrides required rpaths in given executable / library. + """ + rpaths = ['$ORIGIN/'] + existing_rpaths = [] + if not override: + existing_rpaths = linux_get_rpaths(executable_path) + rpaths.extend(existing_rpaths) + + if linux_needs_qt_rpath(executable_path) and qt_rpath not in existing_rpaths: + rpaths.append(qt_rpath) + + rpaths_string = ':'.join(rpaths) + linux_set_rpaths(patchelf, executable_path, rpaths_string) + def memoize(function): """ Decorator to wrap a function with a memoizing callable. @@ -1076,9 +1073,27 @@ def get_python_dict(python_script_path): "file: {}.".format(python_script_path)) raise -def install_pip_dependencies(env_pip, packages): +def install_pip_wheel_package(env_pip): + # Need to install an unreleased wheel version, due to a bug that + # will generate a wheel which will not be installable. + # See https://github.com/pypa/wheel/issues/263 + wheel_url = "git+https://github.com/pypa/wheel.git@fbf3e3ada64d36ca7bb9c1422f5a1ccdba7e4dcf" + install_pip_package_from_url_specifier(env_pip, wheel_url) + +def install_pip_package_from_url_specifier(env_pip, url, upgrade=True): + args = [env_pip, "install", url] + if upgrade: + args.append("--upgrade") + args.append(url) + run_instruction(args, "Failed to install {}".format(url)) + +def install_pip_dependencies(env_pip, packages, upgrade=True): for p in packages: - run_instruction([env_pip, "install", p], "Failed to install " + p) + args = [env_pip, "install"] + if upgrade: + args.append("--upgrade") + args.append(p) + run_instruction(args, "Failed to install " + p) def get_qtci_virtualEnv(python_ver, host, hostArch, targetArch): _pExe = "python" @@ -1105,9 +1120,11 @@ def get_qtci_virtualEnv(python_ver, host, hostArch, targetArch): _pExe = "python3" return(_pExe, _env, env_pip, env_python) -def run_instruction(instruction, error): +def run_instruction(instruction, error, initial_env=None): + if initial_env is None: + initial_env = os.environ print("Running Coin instruction: " + ' '.join(str(e) for e in instruction)) - result = subprocess.call(instruction) + result = subprocess.call(instruction, env=initial_env) if result != 0: print("ERROR : " + error) exit(result) @@ -1124,3 +1141,13 @@ def acceptCITestConfiguration(hostOS, hostOSVer, targetArch, compiler): print("Disabled " + compiler + " to " + targetArch + " from Coin configuration") return False return True + + +def get_ci_qmake_path(ci_install_dir, ci_host_os): + qmake_path = "--qmake={}".format(ci_install_dir) + if ci_host_os == "MacOS": + return qmake_path + "/bin/qmake" + elif ci_host_os == "Windows": + return qmake_path + "\\bin\\qmake.exe" + else: + return qmake_path + "/bin/qmake" diff --git a/coin_build_instructions.py b/coin_build_instructions.py index 9f9a74bc9..1104e996c 100644 --- a/coin_build_instructions.py +++ b/coin_build_instructions.py @@ -36,14 +36,16 @@ ## $QT_END_LICENSE$ ## ############################################################################# -from build_scripts.utils import has_option -from build_scripts.utils import option_value +from build_scripts.options import has_option +from build_scripts.options import option_value from build_scripts.utils import install_pip_dependencies +from build_scripts.utils import install_pip_wheel_package from build_scripts.utils import get_qtci_virtualEnv from build_scripts.utils import run_instruction from build_scripts.utils import rmtree from build_scripts.utils import get_python_dict from build_scripts.utils import acceptCITestConfiguration +from build_scripts.utils import get_ci_qmake_path import os # Values must match COIN thrift @@ -98,19 +100,17 @@ def call_setup(python_ver): _pExe, _env, env_pip, env_python = get_qtci_virtualEnv(python_ver, CI_HOST_OS, CI_HOST_ARCH, CI_TARGET_ARCH) rmtree(_env, True) run_instruction(["virtualenv", "-p", _pExe, _env], "Failed to create virtualenv") - install_pip_dependencies(env_pip, ["six", "wheel"]) - cmd = [env_python, "setup.py"] + + install_pip_dependencies(env_pip, ["six", "setuptools"]) + install_pip_wheel_package(env_pip) + + cmd = [env_python, "-u", "setup.py"] if CI_RELEASE_CONF: cmd += ["bdist_wheel", "--standalone"] else: cmd += ["build"] - if CI_HOST_OS == "MacOS": - cmd += ["--qmake=" + CI_ENV_INSTALL_DIR + "/bin/qmake"] - elif CI_HOST_OS == "Windows": - - cmd += ["--qmake=" + CI_ENV_INSTALL_DIR + "\\bin\\qmake.exe"] - else: - cmd += ["--qmake=" + CI_ENV_INSTALL_DIR + "/bin/qmake"] + qmake_path = get_ci_qmake_path(CI_ENV_INSTALL_DIR, CI_HOST_OS) + cmd.append(qmake_path) cmd += ["--build-tests", "--jobs=4", "--verbose-build"] @@ -121,7 +121,23 @@ def call_setup(python_ver): cmd += ["--package-timestamp=" + CI_INTEGRATION_ID] - run_instruction(cmd, "Failed to run setup.py") + env = os.environ + if CI_HOST_OS == "MacOS": + # On Python 3, setuptools.dist.handle_display_options does some + # weird sys.stdout.detach-ing if the stdout encoding is + # different from utf-8. This causes issues when running + # subprocess.call() because that access the original stdout + # object stored in sys.__stdout__ which was detached, and + # results in an exception being thrown. + # The Coin macOS locale by default is US-ASCII, and that + # triggers the above issue. Set the encoding to UTF-8 which + # makes sure to skip over the detach-ing code. + # Relevant links to the issue: + # https://bugs.python.org/issue15216 + # https://bitbucket.org/tarek/distribute/issues/334/fix-for-311-breaks-packages-that-use + # https://github.com/pypa/virtualenv/issues/359 + env['LC_CTYPE'] = 'UTF-8' + run_instruction(cmd, "Failed to run setup.py", initial_env=env) def run_build_instructions(): if not acceptCITestConfiguration(CI_HOST_OS, CI_HOST_OS_VER, CI_TARGET_ARCH, CI_COMPILER): diff --git a/coin_test_instructions.py b/coin_test_instructions.py index c2c5c07ea..a61a1c9fa 100644 --- a/coin_test_instructions.py +++ b/coin_test_instructions.py @@ -36,13 +36,15 @@ ## $QT_END_LICENSE$ ## ############################################################################# -from build_scripts.utils import has_option -from build_scripts.utils import option_value +from build_scripts.options import has_option +from build_scripts.options import option_value from build_scripts.utils import install_pip_dependencies +from build_scripts.utils import install_pip_wheel_package from build_scripts.utils import get_qtci_virtualEnv from build_scripts.utils import run_instruction from build_scripts.utils import rmtree from build_scripts.utils import acceptCITestConfiguration +from build_scripts.utils import get_ci_qmake_path import os # Values must match COIN thrift @@ -66,12 +68,21 @@ def call_testrunner(python_ver, buildnro): _pExe, _env, env_pip, env_python = get_qtci_virtualEnv(python_ver, CI_HOST_OS, CI_HOST_ARCH, CI_TARGET_ARCH) rmtree(_env, True) run_instruction(["virtualenv", "-p", _pExe, _env], "Failed to create virtualenv") - install_pip_dependencies(env_pip, ["six", "wheel"]) + install_pip_dependencies(env_pip, ["six", "setuptools"]) + install_pip_wheel_package(env_pip) cmd = [env_python, "testrunner.py", "test", "--blacklist", "build_history/blacklist.txt", "--buildno=" + buildnro] run_instruction(cmd, "Failed to run testrunner.py") + qmake_path = get_ci_qmake_path(CI_ENV_INSTALL_DIR, CI_HOST_OS) + + # Try to install built wheels, and build some buildable examples. + if CI_RELEASE_CONF: + wheel_tester_path = os.path.join("testing", "wheel_tester.py") + cmd = [env_python, wheel_tester_path, qmake_path] + run_instruction(cmd, "Error while running wheel_tester.py") + def run_test_instructions(): if not acceptCITestConfiguration(CI_HOST_OS, CI_HOST_OS_VER, CI_TARGET_ARCH, CI_COMPILER): exit() diff --git a/examples/samplebinding/CMakeLists.txt b/examples/samplebinding/CMakeLists.txt index 03ab85754..3852ed36f 100644 --- a/examples/samplebinding/CMakeLists.txt +++ b/examples/samplebinding/CMakeLists.txt @@ -40,7 +40,11 @@ set(generated_sources # ================================== Shiboken detection ====================================== - +# Use provided python interpreter if given. +if(NOT python_interpreter) + find_program(python_interpreter "python") +endif() +message(STATUS "Using python interpreter: ${python_interpreter}") # Macro to get various pyside / python include / link flags and paths. # Uses the not entirely supported utils/pyside2_config.py file. @@ -52,7 +56,8 @@ macro(pyside2_config option output_var) endif() execute_process( - COMMAND python "${CMAKE_SOURCE_DIR}/../utils/pyside2_config.py" ${option} + COMMAND ${python_interpreter} "${CMAKE_SOURCE_DIR}/../utils/pyside2_config.py" + ${option} OUTPUT_VARIABLE ${output_var} OUTPUT_STRIP_TRAILING_WHITESPACE) @@ -64,14 +69,15 @@ macro(pyside2_config option output_var) endif() endmacro() -# Query for the shiboken path, Python path, include paths and linker flags. -pyside2_config(--pyside2 pyside2_path) -pyside2_config(--python-include python_include_dir) -pyside2_config(--shiboken-include shiboken_include_dir 1) -pyside2_config(--shiboken-shared-libraries-cmake shiboken_shared_libraries 0) -pyside2_config(--python-link-cmake python_linking_data 0) +# Query for the shiboken generator path, Python path, include paths and linker flags. +pyside2_config(--shiboken2-module-path shiboken2_module_path) +pyside2_config(--shiboken2-generator-path shiboken2_generator_path) +pyside2_config(--python-include-path python_include_dir) +pyside2_config(--shiboken2-generator-include-path shiboken_include_dir 1) +pyside2_config(--shiboken2-module-shared-libraries-cmake shiboken_shared_libraries 0) +pyside2_config(--python-link-flags-cmake python_linking_data 0) -set(shiboken_path "${pyside2_path}/shiboken2${CMAKE_EXECUTABLE_SUFFIX}") +set(shiboken_path "${shiboken2_generator_path}/shiboken2${CMAKE_EXECUTABLE_SUFFIX}") if(NOT EXISTS ${shiboken_path}) message(FATAL_ERROR "Shiboken executable not found at path: ${shiboken_path}") endif() @@ -87,7 +93,7 @@ endif() # Enable rpaths so that the built shared libraries find their dependencies. set(CMAKE_SKIP_BUILD_RPATH FALSE) set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) -set(CMAKE_INSTALL_RPATH ${pyside2_path} ${CMAKE_CURRENT_SOURCE_DIR}) +set(CMAKE_INSTALL_RPATH ${shiboken2_module_path} ${CMAKE_CURRENT_SOURCE_DIR}) set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) # ============================================================================================= # !!! End of dubious section. diff --git a/examples/scriptableapplication/CMakeLists.txt b/examples/scriptableapplication/CMakeLists.txt index 4119b6756..215d08961 100644 --- a/examples/scriptableapplication/CMakeLists.txt +++ b/examples/scriptableapplication/CMakeLists.txt @@ -14,6 +14,12 @@ set(CMAKE_CXX_STANDARD 11) # Find required Qt packages. find_package(Qt5 5.9 REQUIRED COMPONENTS Core Gui Widgets) +# Use provided python interpreter if given. +if(NOT python_interpreter) + find_program(python_interpreter "python") +endif() +message(STATUS "Using python interpreter: ${python_interpreter}") + # Macro to get various pyside / python include / link flags. macro(pyside2_config option output_var) if(${ARGC} GREATER 2) @@ -23,7 +29,8 @@ macro(pyside2_config option output_var) endif() execute_process( - COMMAND python "${CMAKE_SOURCE_DIR}/../utils/pyside2_config.py" ${option} + COMMAND ${python_interpreter} "${CMAKE_SOURCE_DIR}/../utils/pyside2_config.py" + ${option} OUTPUT_VARIABLE ${output_var} OUTPUT_STRIP_TRAILING_WHITESPACE) @@ -35,14 +42,20 @@ macro(pyside2_config option output_var) endif() endmacro() -# Query for the PySide2 path, Python path, include paths and linker flags. -pyside2_config(--pyside2 PYSIDE2_PATH) -pyside2_config(--python-include PYTHON_INCLUDE_DIR) -pyside2_config(--pyside2-include PYSIDE2_INCLUDE_DIR 1) +# Query for the shiboken2-generator path, PySide2 path, Python path, include paths and linker flags. +pyside2_config(--shiboken2-module-path SHIBOKEN2_MODULE_PATH) +pyside2_config(--shiboken2-generator-path SHIBOKEN2_GENERATOR_PATH) +pyside2_config(--pyside2-path PYSIDE2_PATH) + +pyside2_config(--python-include-path PYTHON_INCLUDE_DIR) +pyside2_config(--shiboken2-generator-include-path SHIBOKEN2_GENERATOR_INCLUDE_DIR 1) +pyside2_config(--pyside2-include-path PYSIDE2_INCLUDE_DIR 1) + +pyside2_config(--python-link-flags-cmake PYTHON_LINKING_DATA 0) +pyside2_config(--shiboken2-module-shared-libraries-cmake SHIBOKEN2_MODULE_SHARED_LIBRARIES 0) pyside2_config(--pyside2-shared-libraries-cmake PYSIDE2_SHARED_LIBRARIES 0) -pyside2_config(--python-link-cmake PYTHON_LINKING_DATA 0) -set(SHIBOKEN_PATH "${PYSIDE2_PATH}/shiboken2${CMAKE_EXECUTABLE_SUFFIX}") +set(SHIBOKEN_PATH "${SHIBOKEN2_GENERATOR_PATH}/shiboken2${CMAKE_EXECUTABLE_SUFFIX}") if(NOT EXISTS ${SHIBOKEN_PATH}) message(FATAL_ERROR "Shiboken executable not found at path: ${SHIBOKEN_PATH}") @@ -122,7 +135,7 @@ endforeach() # Enable rpaths so that the example can be executed from the build dir. set(CMAKE_SKIP_BUILD_RPATH FALSE) set(CMAKE_BUILD_WITH_INSTALL_RPATH TRUE) -set(CMAKE_INSTALL_RPATH ${PYSIDE2_PATH}) +set(CMAKE_INSTALL_RPATH ${PYSIDE2_PATH} ${SHIBOKEN2_MODULE_PATH}) set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) # ============================================================================================= # !!! End of dubious section. @@ -139,11 +152,13 @@ target_sources(${PROJECT_NAME} PUBLIC ${SOURCES}) # Apply relevant include and link flags. target_include_directories(${PROJECT_NAME} PRIVATE ${PYTHON_INCLUDE_DIR}) +target_include_directories(${PROJECT_NAME} PRIVATE ${SHIBOKEN2_GENERATOR_INCLUDE_DIR}) target_include_directories(${PROJECT_NAME} PRIVATE ${PYSIDE2_INCLUDE_DIR}) target_include_directories(${PROJECT_NAME} PRIVATE ${PYSIDE2_ADDITIONAL_INCLUDES}) target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_SOURCE_DIR}) target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Widgets) +target_link_libraries(${PROJECT_NAME} PRIVATE ${SHIBOKEN2_MODULE_SHARED_LIBRARIES}) target_link_libraries(${PROJECT_NAME} PRIVATE ${PYSIDE2_SHARED_LIBRARIES}) # Find and link to the python library. @@ -176,7 +191,8 @@ if(WIN32) # Add custom target to hard link PySide2 shared libraries (just like in qmake example), so you # don't have to set PATH manually to point to the PySide2 package. - foreach(LIBRARY_PATH ${PYSIDE2_SHARED_LIBRARIES}) + set(shared_libraries ${SHIBOKEN2_MODULE_SHARED_LIBRARIES} ${PYSIDE2_SHARED_LIBRARIES}) + foreach(LIBRARY_PATH ${shared_libraries}) string(REGEX REPLACE ".lib$" ".dll" LIBRARY_PATH ${LIBRARY_PATH}) get_filename_component(BASE_NAME ${LIBRARY_PATH} NAME) file(TO_NATIVE_PATH ${LIBRARY_PATH} SOURCE_PATH) diff --git a/examples/scriptableapplication/pyside2.pri b/examples/scriptableapplication/pyside2.pri index 17be4392f..2da3bc880 100644 --- a/examples/scriptableapplication/pyside2.pri +++ b/examples/scriptableapplication/pyside2.pri @@ -1,30 +1,52 @@ PYSIDE_CONFIG = $$PWD/../utils/pyside2_config.py -PYSIDE2 = $$system(python $$PYSIDE_CONFIG --pyside2) +# Use provided python interpreter if given. +isEmpty(python_interpreter) { + python_interpreter = python +} +message(Using python interpreter: $$python_interpreter) + +SHIBOKEN2_GENERATOR = $$system($$python_interpreter $$PYSIDE_CONFIG --shiboken2-generator-path) +isEmpty(SHIBOKEN2_GENERATOR): error(Unable to locate the shiboken2-generator package location) + +SHIBOKEN2_MODULE = $$system($$python_interpreter $$PYSIDE_CONFIG --shiboken2-module-path) +isEmpty(SHIBOKEN2_MODULE): error(Unable to locate the shiboken2 package location) + +PYSIDE2 = $$system($$python_interpreter $$PYSIDE_CONFIG --pyside2-path) isEmpty(PYSIDE2): error(Unable to locate the PySide2 package location) -PYTHON_INCLUDE = $$system(python $$PYSIDE_CONFIG --python-include) +PYTHON_INCLUDE = $$system($$python_interpreter $$PYSIDE_CONFIG --python-include-path) isEmpty(PYTHON_INCLUDE): error(Unable to locate the Python include headers directory) -PYTHON_LFLAGS = $$system(python $$PYSIDE_CONFIG --python-link) +PYTHON_LFLAGS = $$system($$python_interpreter $$PYSIDE_CONFIG --python-link-flags-qmake) isEmpty(PYTHON_LFLAGS): error(Unable to locate the Python library for linking) -PYSIDE2_INCLUDE = $$system(python $$PYSIDE_CONFIG --pyside2-include) +SHIBOKEN2_INCLUDE = $$system($$python_interpreter $$PYSIDE_CONFIG --shiboken2-generator-include-path) +isEmpty(SHIBOKEN2_INCLUDE): error(Unable to locate the shiboken include headers directory) + +PYSIDE2_INCLUDE = $$system($$python_interpreter $$PYSIDE_CONFIG --pyside2-include-path) isEmpty(PYSIDE2_INCLUDE): error(Unable to locate the PySide2 include headers directory) -PYSIDE2_LFLAGS = $$system(python $$PYSIDE_CONFIG --pyside2-link) +SHIBOKEN2_LFLAGS = $$system($$python_interpreter $$PYSIDE_CONFIG --shiboken2-module-qmake-lflags) +isEmpty(SHIBOKEN2_LFLAGS): error(Unable to locate the shiboken libraries for linking) + +PYSIDE2_LFLAGS = $$system($$python_interpreter $$PYSIDE_CONFIG --pyside2-qmake-lflags) isEmpty(PYSIDE2_LFLAGS): error(Unable to locate the PySide2 libraries for linking) -PYSIDE2_SHARED_LIBRARIES = $$system(python $$PYSIDE_CONFIG --pyside2-shared-libraries) +SHIBOKEN2_SHARED_LIBRARIES = $$system($$python_interpreter $$PYSIDE_CONFIG --shiboken2-module-shared-libraries-qmake) +isEmpty(SHIBOKEN2_SHARED_LIBRARIES): error(Unable to locate the used shiboken2 module shared libraries) + +PYSIDE2_SHARED_LIBRARIES = $$system($$python_interpreter $$PYSIDE_CONFIG --pyside2-shared-libraries-qmake) isEmpty(PYSIDE2_SHARED_LIBRARIES): error(Unable to locate the used PySide2 shared libraries) -INCLUDEPATH += "$$PYTHON_INCLUDE" $$PYSIDE2_INCLUDE -LIBS += $$PYTHON_LFLAGS $$PYSIDE2_LFLAGS +INCLUDEPATH += "$$PYTHON_INCLUDE" $$PYSIDE2_INCLUDE $$SHIBOKEN2_INCLUDE +LIBS += $$PYTHON_LFLAGS $$PYSIDE2_LFLAGS $$SHIBOKEN2_LFLAGS !build_pass:message(INCLUDEPATH is $$INCLUDEPATH) !build_pass:message(LIBS are $$LIBS) !build_pass:message(Using $$PYSIDE2) !win32 { - QMAKE_RPATHDIR += $$PYSIDE2 + !build_pass:message(RPATH will include $$PYSIDE2 and $$SHIBOKEN2_MODULE) + QMAKE_RPATHDIR += $$PYSIDE2 $$SHIBOKEN2_MODULE } diff --git a/examples/scriptableapplication/scriptableapplication.pro b/examples/scriptableapplication/scriptableapplication.pro index 8a09b0abf..8ebab9476 100644 --- a/examples/scriptableapplication/scriptableapplication.pro +++ b/examples/scriptableapplication/scriptableapplication.pro @@ -23,7 +23,7 @@ SHIBOKEN_OPTIONS = --generator-set=shiboken --enable-parent-ctor-heuristic \ win32:SHIBOKEN_OPTIONS += --avoid-protected-hack # Prepare the shiboken tool -QT_TOOL.shiboken.binary = $$system_path($$PYSIDE2/shiboken2) +QT_TOOL.shiboken.binary = $$system_path($$SHIBOKEN2_GENERATOR/shiboken2) qtPrepareTool(SHIBOKEN, shiboken) # Shiboken run that adds the module wrapper to GENERATED_SOURCES @@ -61,7 +61,7 @@ win32 { hard_link_libraries.CONFIG = no_link target_predeps explicit_dependencies hard_link_libraries.output = $$out_dir/${QMAKE_FILE_BASE}${QMAKE_FILE_EXT} hard_link_libraries.commands = mklink /H $$shell_path($$out_dir/${QMAKE_FILE_BASE}${QMAKE_FILE_EXT}) $$shell_path(${QMAKE_FILE_IN}) - hard_link_libraries.input = PYSIDE2_SHARED_LIBRARIES + hard_link_libraries.input = PYSIDE2_SHARED_LIBRARIES SHIBOKEN2_SHARED_LIBRARIES } QMAKE_EXTRA_COMPILERS += shiboken module_wrapper_dummy_command diff --git a/examples/utils/pyside2_config.py b/examples/utils/pyside2_config.py index 21a1238b1..c62b38cad 100644 --- a/examples/utils/pyside2_config.py +++ b/examples/utils/pyside2_config.py @@ -41,25 +41,103 @@ import os, glob, re, sys from distutils import sysconfig +generic_error = (' Did you forget to activate your virtualenv? Or perhaps' + ' you forgot to build / install PySide2 into your currently active Python' + ' environment?') +pyside2_error = 'Unable to locate PySide2.' + generic_error +shiboken2_module_error = 'Unable to locate shiboken2-module.' + generic_error +shiboken2_generator_error = 'Unable to locate shiboken2-generator.' + generic_error +pyside2_libs_error = 'Unable to locate the PySide2 shared libraries.' + generic_error +python_link_error = 'Unable to locate the Python library for linking.' +python_include_error = 'Unable to locate the Python include headers directory.' + +options = [] + +# option, function, error, description +options.append(("--shiboken2-module-path", + lambda: find_shiboken2_module(), + shiboken2_module_error, + "Print shiboken2 module location")) +options.append(("--shiboken2-generator-path", + lambda: find_shiboken2_generator(), + shiboken2_generator_error, + "Print shiboken2 generator location")) +options.append(("--pyside2-path", lambda: find_pyside2(), pyside2_error, + "Print PySide2 location")) + +options.append(("--python-include-path", + lambda: get_python_include_path(), + python_include_error, + "Print Python include path")) +options.append(("--shiboken2-generator-include-path", + lambda: get_package_include_path(Package.shiboken2_generator), + pyside2_error, + "Print shiboken2 generator include paths")) +options.append(("--pyside2-include-path", + lambda: get_package_include_path(Package.pyside2), + pyside2_error, + "Print PySide2 include paths")) + +options.append(("--python-link-flags-qmake", lambda: python_link_flags_qmake(), python_link_error, + "Print python link flags for qmake")) +options.append(("--python-link-flags-cmake", lambda: python_link_flags_cmake(), python_link_error, + "Print python link flags for cmake")) + +options.append(("--shiboken2-module-qmake-lflags", + lambda: get_package_qmake_lflags(Package.shiboken2_module), pyside2_error, + "Print shiboken2 shared library link flags for qmake")) +options.append(("--pyside2-qmake-lflags", + lambda: get_package_qmake_lflags(Package.pyside2), pyside2_error, + "Print PySide2 shared library link flags for qmake")) + +options.append(("--shiboken2-module-shared-libraries-qmake", + lambda: get_shared_libraries_qmake(Package.shiboken2_module), pyside2_libs_error, + "Print paths of shiboken2 shared libraries (.so's, .dylib's, .dll's) for qmake")) +options.append(("--shiboken2-module-shared-libraries-cmake", + lambda: get_shared_libraries_cmake(Package.shiboken2_module), pyside2_libs_error, + "Print paths of shiboken2 shared libraries (.so's, .dylib's, .dll's) for cmake")) + +options.append(("--pyside2-shared-libraries-qmake", + lambda: get_shared_libraries_qmake(Package.pyside2), pyside2_libs_error, + "Print paths of PySide2 shared libraries (.so's, .dylib's, .dll's) for qmake")) +options.append(("--pyside2-shared-libraries-cmake", + lambda: get_shared_libraries_cmake(Package.pyside2), pyside2_libs_error, + "Print paths of PySide2 shared libraries (.so's, .dylib's, .dll's) for cmake")) + +options_usage = '' +for i, (flag, _, _, description) in enumerate(options): + options_usage += ' {:<45} {}'.format(flag, description) + if i < len(options) - 1: + options_usage += '\n' + usage = """ -Utility to determine include/link options of PySide2 and Python for qmake +Utility to determine include/link options of shiboken2/PySide2 and Python for qmake/CMake projects +that would like to embed or build custom shiboken2/PySide2 bindings. Usage: pyside2_config.py [option] Options: - --python-include Print Python include path - --python-link Print Python link flags - --pyside2 Print PySide2 location - --pyside2-include Print PySide2 include paths - --pyside2-link Print PySide2 link flags - --pyside2-shared-libraries Print paths of PySide2 shared libraries (.so's, .dylib's, .dll's) - -a Print all - --help/-h Print this help -""" - -def cleanPath(path): +{} + -a Print all options and their values + --help/-h Print this help +""".format(options_usage) + +option = sys.argv[1] if len(sys.argv) == 2 else '-a' +if option == '-h' or option == '--help': + print(usage) + sys.exit(0) + + +class Package(object): + shiboken2_module = 1 + shiboken2_generator = 2 + pyside2 = 3 + + +def clean_path(path): return path if sys.platform != 'win32' else path.replace('\\', '/') -def sharedLibrarySuffix(): + +def shared_library_suffix(): if sys.platform == 'win32': return 'lib' elif sys.platform == 'darwin': @@ -68,7 +146,8 @@ def sharedLibrarySuffix(): else: return 'so.*' -def importSuffixes(): + +def import_suffixes(): if (sys.version_info >= (3, 4)): import importlib return importlib.machinery.EXTENSION_SUFFIXES @@ -79,25 +158,29 @@ def importSuffixes(): result.append(t[0]) return result -def isDebug(): - debugSuffix = '_d.pyd' if sys.platform == 'win32' else '_d.so' - return any([s.endswith(debugSuffix) for s in importSuffixes()]) -def sharedLibraryGlobPattern(): - glob = '*.' + sharedLibrarySuffix() +def is_debug(): + debug_suffix = '_d.pyd' if sys.platform == 'win32' else '_d.so' + return any([s.endswith(debug_suffix) for s in import_suffixes()]) + + +def shared_library_glob_pattern(): + glob = '*.' + shared_library_suffix() return glob if sys.platform == 'win32' else 'lib' + glob -def filterPySide2SharedLibraries(list, only_shiboken=False): - def predicate(item): - basename = os.path.basename(item) - if 'shiboken' in basename or ('pyside2' in basename and not only_shiboken): + +def filter_shared_libraries(libs_list): + def predicate(lib_name): + basename = os.path.basename(lib_name) + if 'shiboken' in basename or 'pyside2' in basename: return True return False - result = [item for item in list if predicate(item)] + result = [lib for lib in libs_list if predicate(lib)] return result + # Return qmake link option for a library file name -def linkOption(lib): +def link_option(lib): # On Linux: # Since we cannot include symlinks with wheel packages # we are using an absolute path for the libpyside and libshiboken @@ -112,24 +195,50 @@ def linkOption(lib): link += os.path.splitext(baseName)[0] return link -# Locate PySide2 via package path -def findPySide2(): + +# Locate PySide2 via sys.path package path. +def find_pyside2(): + return find_package_path("PySide2") + + +def find_shiboken2_module(): + return find_package_path("shiboken2") + + +def find_shiboken2_generator(): + return find_package_path("shiboken2_generator") + + +def find_package(which_package): + if which_package == Package.shiboken2_module: + return find_shiboken2_module() + if which_package == Package.shiboken2_generator: + return find_shiboken2_generator() + if which_package == Package.pyside2: + return find_pyside2() + return None + + +def find_package_path(dir_name): for p in sys.path: if 'site-' in p: - pyside2 = os.path.join(p, 'PySide2') - if os.path.exists(pyside2): - return cleanPath(os.path.realpath(pyside2)) + package = os.path.join(p, dir_name) + if os.path.exists(package): + return clean_path(os.path.realpath(package)) return None + # Return version as "3.5" -def pythonVersion(): +def python_version(): return str(sys.version_info[0]) + '.' + str(sys.version_info[1]) -def pythonInclude(): + +def get_python_include_path(): return sysconfig.get_python_inc() -def pythonLinkQmake(): - flags = pythonLinkData() + +def python_link_flags_qmake(): + flags = python_link_data() if sys.platform == 'win32': libdir = flags['libdir'] # This will add the "~1" shortcut for directories that @@ -146,25 +255,27 @@ def pythonLinkQmake(): # Linux and anything else return '-L{} -l{}'.format(flags['libdir'], flags['lib']) -def pythonLinkCmake(): - flags = pythonLinkData() + +def python_link_flags_cmake(): + flags = python_link_data() libdir = flags['libdir'] lib = re.sub(r'.dll$', '.lib', flags['lib']) return '{};{}'.format(libdir, lib) -def pythonLinkData(): + +def python_link_data(): # @TODO Fix to work with static builds of Python libdir = sysconfig.get_config_var('LIBDIR') if libdir is None: libdir = os.path.abspath(os.path.join( sysconfig.get_config_var('LIBDEST'), "..", "libs")) - version = pythonVersion() + version = python_version() version_no_dots = version.replace('.', '') flags = {} flags['libdir'] = libdir if sys.platform == 'win32': - suffix = '_d' if isDebug() else '' + suffix = '_d' if is_debug() else '' flags['lib'] = 'python{}{}'.format(version_no_dots, suffix) elif sys.platform == 'darwin': @@ -173,42 +284,44 @@ def pythonLinkData(): # Linux and anything else else: if sys.version_info[0] < 3: - suffix = '_d' if isDebug() else '' + suffix = '_d' if is_debug() else '' flags['lib'] = 'python{}{}'.format(version, suffix) else: flags['lib'] = 'python{}{}'.format(version, sys.abiflags) return flags -def pyside2Include(only_shiboken=False): - pySide2 = findPySide2() - if pySide2 is None: + +def get_package_include_path(which_package): + package_path = find_package(which_package) + if package_path is None: return None - includes = "{0}/include/shiboken2".format(pySide2) - if not only_shiboken: - includes = includes + " {0}/include/PySide2".format(pySide2) + includes = "{0}/include".format(package_path) return includes -def pyside2Link(): - pySide2 = findPySide2() - if pySide2 is None: + +def get_package_qmake_lflags(which_package): + package_path = find_package(which_package) + if package_path is None: return None - link = "-L{}".format(pySide2) - glob_result = glob.glob(os.path.join(pySide2, sharedLibraryGlobPattern())) - for lib in filterPySide2SharedLibraries(glob_result): + + link = "-L{}".format(package_path) + glob_result = glob.glob(os.path.join(package_path, shared_library_glob_pattern())) + for lib in filter_shared_libraries(glob_result): link += ' ' - link += linkOption(lib) + link += link_option(lib) return link -def pyside2SharedLibrariesData(only_shiboken=False): - pySide2 = findPySide2() - if pySide2 is None: + +def get_shared_libraries_data(which_package): + package_path = find_package(which_package) + if package_path is None: return None - glob_result = glob.glob(os.path.join(pySide2, sharedLibraryGlobPattern())) - filtered_libs = filterPySide2SharedLibraries(glob_result, only_shiboken) + glob_result = glob.glob(os.path.join(package_path, shared_library_glob_pattern())) + filtered_libs = filter_shared_libraries(glob_result) libs = [] if sys.platform == 'win32': for lib in filtered_libs: @@ -218,8 +331,9 @@ def pyside2SharedLibrariesData(only_shiboken=False): libs.append(lib) return libs -def pyside2SharedLibraries(): - libs = pyside2SharedLibrariesData() + +def get_shared_libraries_qmake(which_package): + libs = get_shared_libraries_data(which_package) if libs is None: return None @@ -238,80 +352,21 @@ def pyside2SharedLibraries(): libs_string += lib + ' ' return libs_string -def pyside2SharedLibrariesCmake(only_shiboken=False): - libs = pyside2SharedLibrariesData(only_shiboken) + +def get_shared_libraries_cmake(which_package): + libs = get_shared_libraries_data(which_package) result = ';'.join(libs) return result -option = sys.argv[1] if len(sys.argv) == 2 else '-a' -if option == '-h' or option == '--help': - print(usage) - sys.exit(0) -generic_error = (' Did you forget to activate your virtualenv? Or perhaps' - ' you forgot to build / install PySide2 into your currently active Python' - ' environment?') -pyside2_error = 'Unable to locate PySide2.' + generic_error -pyside2_libs_error = 'Unable to locate the PySide2 shared libraries.' + generic_error -python_link_error = 'Unable to locate the Python library for linking.' +print_all = option == "-a" +for argument, handler, error, _ in options: + if option == argument or print_all: + handler_result = handler() + if handler_result is None: + sys.exit(error) -if option == '--pyside2' or option == '-a': - pySide2 = findPySide2() - if pySide2 is None: - sys.exit(pyside2_error) - print(pySide2) - -if option == '--pyside2-link' or option == '-a': - l = pyside2Link() - if l is None: - sys.exit(pyside2_error) - - print(l) - -if option == '--shiboken-include' or option == '-a': - i = pyside2Include(only_shiboken=True) - if i is None: - sys.exit(pyside2_error) - print(i) - -if option == '--pyside2-include' or option == '-a': - i = pyside2Include() - if i is None: - sys.exit(pyside2_error) - print(i) - -if option == '--python-include' or option == '-a': - i = pythonInclude() - if i is None: - sys.exit('Unable to locate the Python include headers directory.') - print(i) - -if option == '--python-link' or option == '-a': - l = pythonLinkQmake() - if l is None: - sys.exit(python_link_error) - print(l) - -if option == '--python-link-cmake' or option == '-a': - l = pythonLinkCmake() - if l is None: - sys.exit(python_link_error) - print(l) - -if option == '--pyside2-shared-libraries' or option == '-a': - l = pyside2SharedLibraries() - if l is None: - sys.exit(pyside2_libs_error) - print(l) - -if option == '--pyside2-shared-libraries-cmake' or option == '-a': - l = pyside2SharedLibrariesCmake() - if l is None: - sys.exit(pyside2_libs_error) - print(l) - -if option == '--shiboken-shared-libraries-cmake' or option == '-a': - l = pyside2SharedLibrariesCmake(only_shiboken=True) - if l is None: - sys.exit(pyside2_libs_error) - print(l) + line = handler_result + if print_all: + line = "{:<40}: ".format(argument) + line + print(line) diff --git a/keyword-errors.lst b/keyword-errors.lst new file mode 100644 index 000000000..af8c581a5 --- /dev/null +++ b/keyword-errors.lst @@ -0,0 +1,43 @@ +KEYWORD {'multi': None, 'funcname': 'PySide2.QtCore.QAbstractItemModel.changePersistentIndex', 'arglist': 'from:PySide2.QtCore.QModelIndex,to:PySide2.QtCore.QModelIndex', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtCore.QAbstractItemModel.changePersistentIndexList', 'arglist': 'from:QModelIndexList,to:QModelIndexList', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtCore.QByteArray.indexOf', 'arglist': 'a:PySide2.QtCore.QByteArray,from:int=0', 'returntype': 'int'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtCore.QByteArray.lastIndexOf', 'arglist': 'a:PySide2.QtCore.QByteArray,from:int=-1', 'returntype': 'int'} +KEYWORD {'multi': '1', 'funcname': 'PySide2.QtCore.QByteArrayMatcher.indexIn', 'arglist': 'ba:PySide2.QtCore.QByteArray,from:int=0', 'returntype': 'int'} +KEYWORD {'multi': '0', 'funcname': 'PySide2.QtCore.QByteArrayMatcher.indexIn', 'arglist': 'str:str,len:int,from:int=0', 'returntype': 'int'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtCore.QItemSelection.indexOf', 'arglist': 't:PySide2.QtCore.QItemSelectionRange,from:int=0', 'returntype': 'int'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtCore.QItemSelection.lastIndexOf', 'arglist': 't:PySide2.QtCore.QItemSelectionRange,from:int=-1', 'returntype': 'int'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtCore.QItemSelection.move', 'arglist': 'from:int,to:int', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtCore.QTextCodec.convertToUnicode', 'arglist': 'in:str,length:int,state:PySide2.QtCore.QTextCodec.ConverterState', 'returntype': 'QString'} +KEYWORD {'multi': '0', 'funcname': 'PySide2.QtCore.QTextCodec.toUnicode', 'arglist': 'in:str,length:int,state:PySide2.QtCore.QTextCodec.ConverterState=nullptr', 'returntype': 'QString'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtCore.QVariantAnimation.interpolated', 'arglist': 'from:QVariant,to:QVariant,progress:double', 'returntype': 'QVariant'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtCore.QXmlStreamAttributes.indexOf', 'arglist': 't:PySide2.QtCore.QXmlStreamAttribute,from:int=0', 'returntype': 'int'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtCore.QXmlStreamAttributes.lastIndexOf', 'arglist': 't:PySide2.QtCore.QXmlStreamAttribute,from:int=-1', 'returntype': 'int'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtCore.QXmlStreamAttributes.move', 'arglist': 'from:int,to:int', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtGui.QAbstractTextDocumentLayout.documentChanged', 'arglist': 'from:int,charsRemoved:int,charsAdded:int', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtGui.QIconEngine.read', 'arglist': 'in:PySide2.QtCore.QDataStream', 'returntype': 'bool'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtGui.QKeySequence.__lshift__', 'arglist': 'in:PySide2.QtCore.QDataStream', 'returntype': 'PySide2.QtCore.QDataStream'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtGui.QPolygon.indexOf', 'arglist': 't:PySide2.QtCore.QPoint,from:int=0', 'returntype': 'int'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtGui.QPolygon.lastIndexOf', 'arglist': 't:PySide2.QtCore.QPoint,from:int=-1', 'returntype': 'int'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtGui.QPolygon.move', 'arglist': 'from:int,to:int', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtGui.QPolygonF.indexOf', 'arglist': 't:PySide2.QtCore.QPointF,from:int=0', 'returntype': 'int'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtGui.QPolygonF.lastIndexOf', 'arglist': 't:PySide2.QtCore.QPointF,from:int=-1', 'returntype': 'int'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtGui.QPolygonF.move', 'arglist': 'from:int,to:int', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtGui.QQuaternion.rotationTo', 'arglist': 'from:PySide2.QtGui.QVector3D,to:PySide2.QtGui.QVector3D', 'returntype': 'PySide2.QtGui.QQuaternion'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtGui.QStandardItem.read', 'arglist': 'in:PySide2.QtCore.QDataStream', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtGui.QStandardItem.__rshift__', 'arglist': 'in:PySide2.QtCore.QDataStream', 'returntype': 'PySide2.QtCore.QDataStream'} +KEYWORD {'multi': '4', 'funcname': 'PySide2.QtGui.QTextDocument.find', 'arglist': 'expr:PySide2.QtCore.QRegExp,from:int=0,options:PySide2.QtGui.QTextDocument.FindFlags=QTextDocument.FindFlags()', 'returntype': 'PySide2.QtGui.QTextCursor'} +KEYWORD {'multi': '2', 'funcname': 'PySide2.QtGui.QTextDocument.find', 'arglist': 'expr:PySide2.QtCore.QRegularExpression,from:int=0,options:PySide2.QtGui.QTextDocument.FindFlags=QTextDocument.FindFlags()', 'returntype': 'PySide2.QtGui.QTextCursor'} +KEYWORD {'multi': '0', 'funcname': 'PySide2.QtGui.QTextDocument.find', 'arglist': 'subString:QString,from:int=0,options:PySide2.QtGui.QTextDocument.FindFlags=QTextDocument.FindFlags()', 'returntype': 'PySide2.QtGui.QTextCursor'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtGui.QTextDocument.markContentsDirty', 'arglist': 'from:int,length:int', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtWidgets.QHeaderView.moveSection', 'arglist': 'from:int,to:int', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtWidgets.QLayout.replaceWidget', 'arglist': 'from:PySide2.QtWidgets.QWidget,to:PySide2.QtWidgets.QWidget,options:PySide2.QtCore.Qt.FindChildOptions=Qt.FindChildrenRecursively', 'returntype': 'PySide2.QtWidgets.QLayoutItem'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtWidgets.QListWidgetItem.read', 'arglist': 'in:PySide2.QtCore.QDataStream', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtWidgets.QListWidgetItem.__rshift__', 'arglist': 'in:PySide2.QtCore.QDataStream', 'returntype': 'PySide2.QtCore.QDataStream'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtWidgets.QPlainTextDocumentLayout.documentChanged', 'arglist': 'from:int,arg__2:int,charsAdded:int', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtWidgets.QTabBar.moveTab', 'arglist': 'from:int,to:int', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtWidgets.QTableWidgetItem.read', 'arglist': 'in:PySide2.QtCore.QDataStream', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtWidgets.QTableWidgetItem.__rshift__', 'arglist': 'in:PySide2.QtCore.QDataStream', 'returntype': 'PySide2.QtCore.QDataStream'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtWidgets.QTreeWidgetItem.read', 'arglist': 'in:PySide2.QtCore.QDataStream', 'returntype': None} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtWidgets.QTreeWidgetItem.__rshift__', 'arglist': 'in:PySide2.QtCore.QDataStream', 'returntype': 'PySide2.QtCore.QDataStream'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtMultimedia.QAudio.convertVolume', 'arglist': 'volume:double,from:PySide2.QtMultimedia.QAudio.VolumeScale,to:PySide2.QtMultimedia.QAudio.VolumeScale', 'returntype': 'double'} +KEYWORD {'multi': None, 'funcname': 'PySide2.QtMultimedia.QMediaPlaylist.moveMedia', 'arglist': 'from:int,to:int', 'returntype': 'bool'} diff --git a/popenasync.py b/popenasync.py deleted file mode 100644 index 77faf9e0c..000000000 --- a/popenasync.py +++ /dev/null @@ -1,360 +0,0 @@ -############################################################################# -## -## Copyright (C) 2017 The Qt Company Ltd. -## Contact: https://www.qt.io/licensing/ -## -## This file is part of Qt for Python. -## -## $QT_BEGIN_LICENSE:LGPL$ -## Commercial License Usage -## Licensees holding valid commercial Qt licenses may use this file in -## accordance with the commercial license agreement provided with the -## Software or, alternatively, in accordance with the terms contained in -## a written agreement between you and The Qt Company. For licensing terms -## and conditions see https://www.qt.io/terms-conditions. For further -## information use the contact form at https://www.qt.io/contact-us. -## -## GNU Lesser General Public License Usage -## Alternatively, this file may be used under the terms of the GNU Lesser -## General Public License version 3 as published by the Free Software -## Foundation and appearing in the file LICENSE.LGPL3 included in the -## packaging of this file. Please review the following information to -## ensure the GNU Lesser General Public License version 3 requirements -## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. -## -## GNU General Public License Usage -## Alternatively, this file may be used under the terms of the GNU -## General Public License version 2.0 or (at your option) the GNU General -## Public license version 3 or any later version approved by the KDE Free -## Qt Foundation. The licenses are as published by the Free Software -## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 -## included in the packaging of this file. Please review the following -## information to ensure the GNU General Public License requirements will -## be met: https://www.gnu.org/licenses/gpl-2.0.html and -## https://www.gnu.org/licenses/gpl-3.0.html. -## -## $QT_END_LICENSE$ -## -############################################################################# - -################################################################################ -""" - -Modification of http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/440554 - -""" - -#################################### IMPORTS ################################### - -import os -import subprocess -import errno -import time -import sys -import unittest -import tempfile - -def geterror (): - return sys.exc_info()[1] - -if sys.version_info >= (3,): - null_byte = '\x00'.encode('ascii') -else: - null_byte = '\x00' - -mswindows = (sys.platform == "win32") - -if mswindows: - if sys.version_info >= (3,): - # Test date should be in ascii. - def encode(s): - return s.encode('ascii', 'ignore') - - def decode(b): - return b.decode('ascii', 'ignore') - else: - # Strings only; do nothing - def encode(s): - return s - - def decode(b): - return b - - try: - import ctypes - from ctypes.wintypes import DWORD - kernel32 = ctypes.windll.kernel32 - TerminateProcess = ctypes.windll.kernel32.TerminateProcess - def WriteFile(handle, data, ol = None): - c_written = DWORD() - success = ctypes.windll.kernel32.WriteFile(handle, - ctypes.create_string_buffer(encode(data)), len(data), - ctypes.byref(c_written), ol) - return ctypes.windll.kernel32.GetLastError(), c_written.value - def ReadFile(handle, desired_bytes, ol = None): - c_read = DWORD() - buffer = ctypes.create_string_buffer(desired_bytes+1) - success = ctypes.windll.kernel32.ReadFile(handle, buffer, - desired_bytes, ctypes.byref(c_read), ol) - buffer[c_read.value] = null_byte - return ctypes.windll.kernel32.GetLastError(), decode(buffer.value) - def PeekNamedPipe(handle, desired_bytes): - c_avail = DWORD() - c_message = DWORD() - if desired_bytes > 0: - c_read = DWORD() - buffer = ctypes.create_string_buffer(desired_bytes+1) - success = ctypes.windll.kernel32.PeekNamedPipe(handle, buffer, - desired_bytes, ctypes.byref(c_read), ctypes.byref(c_avail), - ctypes.byref(c_message)) - buffer[c_read.value] = null_byte - return decode(buffer.value), c_avail.value, c_message.value - else: - success = ctypes.windll.kernel32.PeekNamedPipe(handle, None, - desired_bytes, None, ctypes.byref(c_avail), - ctypes.byref(c_message)) - return "", c_avail.value, c_message.value - - except ImportError: - from win32file import ReadFile, WriteFile - from win32pipe import PeekNamedPipe - from win32api import TerminateProcess - import msvcrt - -else: - from signal import SIGINT, SIGTERM, SIGKILL - import select - import fcntl - -################################### CONSTANTS ################################## - -PIPE = subprocess.PIPE - -################################################################################ - -class Popen(subprocess.Popen): - def __init__(self, *args, **kwargs): - subprocess.Popen.__init__(self, *args, **kwargs) - - def recv(self, maxsize=None): - return self._recv('stdout', maxsize) - - def recv_err(self, maxsize=None): - return self._recv('stderr', maxsize) - - def send_recv(self, input='', maxsize=None): - return self.send(input), self.recv(maxsize), self.recv_err(maxsize) - - def read_async(self, wait=.1, e=1, tr=5, stderr=0): - if tr < 1: - tr = 1 - x = time.time()+ wait - y = [] - r = '' - pr = self.recv - if stderr: - pr = self.recv_err - while time.time() < x or r: - r = pr() - if r is None: - if e: - raise Exception("Other end disconnected!") - else: - break - elif r: - y.append(r) - else: - time.sleep(max((x-time.time())/tr, 0)) - return ''.join(y) - - def send_all(self, data): - while len(data): - sent = self.send(data) - if sent is None: - raise Exception("Other end disconnected!") - data = buffer(data, sent) - - def get_conn_maxsize(self, which, maxsize): - if maxsize is None: - maxsize = 1024 - elif maxsize < 1: - maxsize = 1 - return getattr(self, which), maxsize - - def _close(self, which): - conn = getattr(self, which) - flags = fcntl.fcntl(conn, fcntl.F_GETFL) - if not conn.closed: - fcntl.fcntl(conn, fcntl.F_SETFL, flags & ~os.O_NONBLOCK) - assert conn.read() == '' - getattr(self, which).close() - setattr(self, which, None) - - if mswindows: - def kill(self): - # Recipes - #http://me.in-berlin.de/doc/python/faq/windows.html#how-do-i-emulate-os-kill-in-windows - #http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/347462 - - """kill function for Win32""" - TerminateProcess(int(self._handle), 0) # returns None - - def send(self, input): - if not self.stdin: - return None - - try: - x = msvcrt.get_osfhandle(self.stdin.fileno()) - (errCode, written) = WriteFile(x, input) - except ValueError: - return self._close('stdin') - except (subprocess.pywintypes.error, Exception): - if geterror()[0] in (109, errno.ESHUTDOWN): - return self._close('stdin') - raise - - return written - - def _recv(self, which, maxsize): - conn, maxsize = self.get_conn_maxsize(which, maxsize) - if conn is None: - return None - - try: - x = msvcrt.get_osfhandle(conn.fileno()) - (read, nAvail, nMessage) = PeekNamedPipe(x, 0) - if maxsize < nAvail: - nAvail = maxsize - if nAvail > 0: - (errCode, read) = ReadFile(x, nAvail, None) - except ValueError: - return self._close(which) - except (subprocess.pywintypes.error, Exception): - if geterror()[0] in (109, errno.ESHUTDOWN): - return self._close(which) - raise - - if self.universal_newlines: - # Translate newlines. For Python 3.x assume read is text. - # If bytes then another solution is needed. - read = read.replace("\r\n", "\n").replace("\r", "\n") - return read - - else: - def kill(self): - for i, sig in enumerate([SIGTERM, SIGKILL] * 2): - if i % 2 == 0: os.kill(self.pid, sig) - time.sleep((i * (i % 2) / 5.0) + 0.01) - - killed_pid, stat = os.waitpid(self.pid, os.WNOHANG) - if killed_pid != 0: return - - def send(self, input): - if not self.stdin: - return None - - if not select.select([], [self.stdin], [], 0)[1]: - return 0 - - try: - written = os.write(self.stdin.fileno(), input) - except OSError: - if geterror()[0] == errno.EPIPE: #broken pipe - return self._close('stdin') - raise - - return written - - def _recv(self, which, maxsize): - conn, maxsize = self.get_conn_maxsize(which, maxsize) - if conn is None: - return None - - flags = fcntl.fcntl(conn, fcntl.F_GETFL) - if not conn.closed: - fcntl.fcntl(conn, fcntl.F_SETFL, flags| os.O_NONBLOCK) - - try: - if not select.select([conn], [], [], 0)[0]: - return '' - - try: - r = conn.read(maxsize) - except IOError as e: - if e.errno == errno.EAGAIN: - return '' - raise - if not r: - return self._close(which) - - if self.universal_newlines: - r = r.replace("\r\n", "\n").replace("\r", "\n") - return r - finally: - if not conn.closed: - fcntl.fcntl(conn, fcntl.F_SETFL, flags) - -################################################################################ - -def proc_in_time_or_kill(cmd, time_out, wd = None, env = None): - proc = Popen ( - cmd, cwd = wd, env = env, - stdin = subprocess.PIPE, stdout = subprocess.PIPE, - stderr = subprocess.STDOUT, universal_newlines = 1 - ) - - ret_code = None - response = [] - - t = time.time() - while ret_code is None and ((time.time() -t) < time_out): - ret_code = proc.poll() - response += [proc.read_async(wait=0.1, e=0)] - - if ret_code is None: - ret_code = '"Process timed out (time_out = {} secs) '.format(time_out) - try: - proc.kill() - ret_code += 'and was successfully terminated"' - except Exception: - ret_code += ("and termination failed " - "(exception: {})".format(geterror(),)) - - return ret_code, ''.join(response) - -################################################################################ - -class AsyncTest(unittest.TestCase): - def test_proc_in_time_or_kill(self): - ret_code, response = proc_in_time_or_kill( - [sys.executable, '-c', 'while 1: pass'], time_out = 1 - ) - - self.assert_( 'rocess timed out' in ret_code ) - self.assert_( 'successfully terminated' in ret_code ) - -################################################################################ - -def _example(): - if sys.platform == 'win32': - shell, commands, tail = ('cmd', ('echo "hello"', 'echo "HELLO WORLD"'), - '\r\n') - else: - shell, commands, tail = ('sh', ('ls', 'echo HELLO WORLD'), '\n') - - a = Popen(shell, stdin=PIPE, stdout=PIPE) - sys.stdout.write(a.read_async()) - sys.stdout.write(" ") - for cmd in commands: - a.send_all(cmd + tail) - sys.stdout.write(a.read_async()) - sys.stdout.write(" ") - a.send_all('exit' + tail) - print (a.read_async(e=0)) - a.wait() - -################################################################################ - -if __name__ == '__main__': - if 1: unittest.main() - else: _example() @@ -42,12 +42,18 @@ from __future__ import print_function """ This is a distutils setup-script for the Qt for Python project -To build PySide2 simply execute: +To build both shiboken2 and PySide2 simply execute: python setup.py build or python setup.py install to build and install into your current Python installation. +The same setup.py script is used to build all the components of the +project: + - shiboken2 (the supporting Python module) + - shiboken2-generator (the bindings generation executable) + - PySide2 + - pyside2-tools Optionally, one can specify the location of qmake and cmake if it is not on the current PATH with: @@ -56,6 +62,23 @@ and --cmake=/path/to/bin/cmake respectively. +By default, all of the above is built when no special options are +passed to the script. You can use the --build-type parameter to specify +which things should be built: + --build-type=shiboken2 - build / package only the python module + --build-type=shiboken2-generator - build / package the generator + executable + --build-type=pyside2 - build / package the PySide2 bindings and + and pyside2-tools + --build-type=all - the implicit default to build all of the above + + +When building PySide2, optionally, one can specify the location of the +shiboken2 cmake config path if it is not on the current PATH with: + --shiboken-config-dir=/path/to/shiboken/cmake/config/dir +This is useful if you did a cmake installation of shiboken2 into +a custom location. + For Windows, if OpenSSL support is required, it's necessary to specify the directory path that contains the OpenSSL shared libraries "libeay32.dll" and "ssleay32.dll", for example: @@ -79,7 +102,7 @@ not specified. You can use the option `--only-package` if you want to create more binary packages (bdist_wheel, bdist_egg, ...) without rebuilding the -entire PySide2 every time: +entire project every time: e.g.: @@ -89,7 +112,7 @@ e.g.: --cmake=c:\tools\cmake\bin\cmake.exe --openssl=c:\libs\OpenSSL32bit\bin -* Then, we create a bdist_egg reusing PySide2 build with option +* Then, we create a bdist_egg reusing the PySide2 build with option `--only-package`: python setup.py bdist_egg --only-package @@ -110,7 +133,7 @@ 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 usining the internal `qt.conf` file. +or "0" (or leave empty) to keep using the internal `qt.conf` file. DEVELOPMENT OPTIONS: @@ -230,78 +253,18 @@ this_file = os.path.abspath(this_file) if os.path.dirname(this_file): os.chdir(os.path.dirname(this_file)) -from build_scripts.main import get_package_version, get_setuptools_extension_modules -from build_scripts.main import pyside_package_dir_name -from build_scripts.main import cmd_class_dict -from build_scripts.main import README, CHANGES -from setuptools import setup, Extension +# Save the original command line arguments to pass them on to the setup +# mechanism. +original_argv = list(sys.argv) -# The __version__ variable is just for PEP compliancy, and shouldn't be -# used as a value source. +from build_scripts.main import get_package_version, check_allowed_python_version +from build_scripts.setup_runner import SetupRunner + +# The __version__ variable is just for PEP compliance, and shouldn't be +# used as a value source. Use get_package_version() instead. __version__ = get_package_version() -extension_modules = get_setuptools_extension_modules() - -setup( - name = "PySide2", - version = get_package_version(), - description = ("Python bindings for the Qt cross-platform application and " - "UI framework"), - long_description = README + "\n\n" + CHANGES, - long_description_content_type = 'text/markdown', - classifiers = [ - 'Development Status :: 5 - Production/Stable', - 'Environment :: Console', - 'Environment :: MacOS X', - 'Environment :: X11 Applications :: Qt', - 'Environment :: Win32 (MS Windows)', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: POSIX', - 'Operating System :: POSIX :: Linux', - 'Operating System :: Microsoft', - 'Operating System :: Microsoft :: Windows', - 'Programming Language :: C++', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Topic :: Database', - 'Topic :: Software Development', - 'Topic :: Software Development :: Code Generators', - 'Topic :: Software Development :: Libraries :: Application Frameworks', - 'Topic :: Software Development :: User Interfaces', - 'Topic :: Software Development :: Widget Sets', - ], - keywords = 'Qt', - author = 'Qt for Python Team', - author_email = 'pyside@qt-project.org', - url = 'https://www.pyside.org', - download_url = 'https://download.qt.io/official_releases/QtForPython/', - license = 'LGPL', - packages = ['PySide2', 'pyside2uic', - 'pyside2uic.Compiler', - 'pyside2uic.port_v{}'.format(sys.version_info[0]) ], - package_dir = {'': pyside_package_dir_name}, - include_package_data = True, - zip_safe = False, - entry_points = { - 'console_scripts': [ - 'pyside2-uic = PySide2.scripts.uic:main', - 'pyside2-rcc = PySide2.scripts.pyside_tool:main', - 'pyside2-lupdate = PySide2.scripts.pyside_tool:main', - 'shiboken2 = PySide2.scripts.pyside_tool:main', - ] - }, - cmdclass = cmd_class_dict, - # Add a bogus extension module (will never be built here since we - # are overriding the build command to do it using cmake) so things - # like bdist_egg will know that there are extension modules and - # will name the dist with the full platform info. - ext_modules = extension_modules, - ext_package = 'PySide2', -) +check_allowed_python_version() + +setup_runner = SetupRunner(original_argv) +setup_runner.run_setup() diff --git a/sources/cmake_helpers/helpers.cmake b/sources/cmake_helpers/helpers.cmake new file mode 100644 index 000000000..dd2e98a3a --- /dev/null +++ b/sources/cmake_helpers/helpers.cmake @@ -0,0 +1,63 @@ +macro(compute_config_py_values + full_version_var_name + ) + string(TIMESTAMP PACKAGE_BUILD_DATE "%Y-%m-%dT%H:%M:%S+00:00" UTC) + if (PACKAGE_BUILD_DATE) + set(PACKAGE_BUILD_DATE "__build_date__ = '${PACKAGE_BUILD_DATE}'") + endif() + + if (PACKAGE_SETUP_PY_PACKAGE_VERSION) + set(PACKAGE_SETUP_PY_PACKAGE_VERSION_ASSIGNMENT "__setup_py_package_version__ = '${PACKAGE_SETUP_PY_PACKAGE_VERSION}'") + set(FINAL_PACKAGE_VERSION ${PACKAGE_SETUP_PY_PACKAGE_VERSION}) + else() + set(FINAL_PACKAGE_VERSION ${${full_version_var_name}}) + endif() + + if (PACKAGE_SETUP_PY_PACKAGE_TIMESTAMP) + set(PACKAGE_SETUP_PY_PACKAGE_TIMESTAMP_ASSIGNMENT "__setup_py_package_timestamp__ = '${PACKAGE_SETUP_PY_PACKAGE_TIMESTAMP}'") + else() + set(PACKAGE_SETUP_PY_PACKAGE_TIMESTAMP_ASSIGNMENT "") + endif() + + find_package(Git) + if(GIT_FOUND) + # Check if current source folder is inside a git repo, so that commit information can be + # queried. + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse --git-dir + OUTPUT_VARIABLE PACKAGE_SOURCE_IS_INSIDE_REPO + ERROR_QUIET + OUTPUT_STRIP_TRAILING_WHITESPACE) + + if(PACKAGE_SOURCE_IS_INSIDE_REPO) + # Force git dates to be UTC-based. + set(ENV{TZ} UTC) + execute_process( + COMMAND ${GIT_EXECUTABLE} --no-pager show --date=format-local:%Y-%m-%dT%H:%M:%S+00:00 -s --format=%cd HEAD + OUTPUT_VARIABLE PACKAGE_BUILD_COMMIT_DATE + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(PACKAGE_BUILD_COMMIT_DATE) + set(PACKAGE_BUILD_COMMIT_DATE "__build_commit_date__ = '${PACKAGE_BUILD_COMMIT_DATE}'") + endif() + unset(ENV{TZ}) + + execute_process( + COMMAND ${GIT_EXECUTABLE} rev-parse HEAD + OUTPUT_VARIABLE PACKAGE_BUILD_COMMIT_HASH + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(PACKAGE_BUILD_COMMIT_HASH) + set(PACKAGE_BUILD_COMMIT_HASH "__build_commit_hash__ = '${PACKAGE_BUILD_COMMIT_HASH}'") + endif() + + execute_process( + COMMAND ${GIT_EXECUTABLE} describe HEAD + OUTPUT_VARIABLE PACKAGE_BUILD_COMMIT_HASH_DESCRIBED + OUTPUT_STRIP_TRAILING_WHITESPACE) + if(PACKAGE_BUILD_COMMIT_HASH_DESCRIBED) + set(PACKAGE_BUILD_COMMIT_HASH_DESCRIBED "__build_commit_hash_described__ = '${PACKAGE_BUILD_COMMIT_HASH_DESCRIBED}'") + endif() + + endif() + endif() + +endmacro() diff --git a/sources/pyside2/CMakeLists.txt b/sources/pyside2/CMakeLists.txt index 358c119ae..25598bb5e 100644 --- a/sources/pyside2/CMakeLists.txt +++ b/sources/pyside2/CMakeLists.txt @@ -8,8 +8,10 @@ cmake_policy(SET CMP0046 NEW) project(pysidebindings) -set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Macros/ +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/../cmake_helpers/ + ${CMAKE_SOURCE_DIR}/cmake/Macros/ ${CMAKE_MODULE_PATH}) +include(helpers) option(USE_PYTHON_VERSION "Use specific python version to build pyside2." "") @@ -244,64 +246,7 @@ else() CACHE STRING "PySide2 version [full]" FORCE) endif() -string(TIMESTAMP PYSIDE_BUILD_DATE "%Y-%m-%dT%H:%M:%S+00:00" UTC) -if (PYSIDE_BUILD_DATE) - set(PYSIDE_BUILD_DATE "__build_date__ = '${PYSIDE_BUILD_DATE}'") -endif() - -if (PYSIDE_SETUP_PY_PACKAGE_VERSION) - set(PYSIDE_SETUP_PY_PACKAGE_VERSION_ASSIGNMENT "__setup_py_package_version__ = '${PYSIDE_SETUP_PY_PACKAGE_VERSION}'") - set(FINAL_PACKAGE_VERSION ${PYSIDE_SETUP_PY_PACKAGE_VERSION}) -else() - set(FINAL_PACKAGE_VERSION ${BINDING_API_VERSION_FULL}) -endif() - -if (PYSIDE_SETUP_PY_PACKAGE_TIMESTAMP) - set(PYSIDE_SETUP_PY_PACKAGE_TIMESTAMP_ASSIGNMENT "__setup_py_package_timestamp__ = '${PYSIDE_SETUP_PY_PACKAGE_TIMESTAMP}'") -else() - set(PYSIDE_SETUP_PY_PACKAGE_TIMESTAMP_ASSIGNMENT "__setup_py_package_timestamp__ = ''") -endif() - -find_package(Git) -if(GIT_FOUND) - # Check if current source folder is inside a git repo, so that commit information can be - # queried. - execute_process( - COMMAND ${GIT_EXECUTABLE} rev-parse --git-dir - OUTPUT_VARIABLE PYSIDE_SOURCE_IS_INSIDE_REPO - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - - if(PYSIDE_SOURCE_IS_INSIDE_REPO) - # Force git dates to be UTC-based. - set(ENV{TZ} UTC) - execute_process( - COMMAND ${GIT_EXECUTABLE} --no-pager show --date=format-local:%Y-%m-%dT%H:%M:%S+00:00 -s --format=%cd HEAD - OUTPUT_VARIABLE PYSIDE_BUILD_COMMIT_DATE - OUTPUT_STRIP_TRAILING_WHITESPACE) - if(PYSIDE_BUILD_COMMIT_DATE) - set(PYSIDE_BUILD_COMMIT_DATE "__build_commit_date__ = '${PYSIDE_BUILD_COMMIT_DATE}'") - endif() - unset(ENV{TZ}) - - execute_process( - COMMAND ${GIT_EXECUTABLE} rev-parse HEAD - OUTPUT_VARIABLE PYSIDE_BUILD_COMMIT_HASH - OUTPUT_STRIP_TRAILING_WHITESPACE) - if(PYSIDE_BUILD_COMMIT_HASH) - set(PYSIDE_BUILD_COMMIT_HASH "__build_commit_hash__ = '${PYSIDE_BUILD_COMMIT_HASH}'") - endif() - - execute_process( - COMMAND ${GIT_EXECUTABLE} describe HEAD - OUTPUT_VARIABLE PYSIDE_BUILD_COMMIT_HASH_DESCRIBED - OUTPUT_STRIP_TRAILING_WHITESPACE) - if(PYSIDE_BUILD_COMMIT_HASH_DESCRIBED) - set(PYSIDE_BUILD_COMMIT_HASH_DESCRIBED "__build_commit_hash_described__ = '${PYSIDE_BUILD_COMMIT_HASH_DESCRIBED}'") - endif() - - endif() -endif() +compute_config_py_values(BINDING_API_VERSION) include(PySideModules) diff --git a/sources/pyside2/PySide2/CMakeLists.txt b/sources/pyside2/PySide2/CMakeLists.txt index 0263f7441..4709dd073 100644 --- a/sources/pyside2/PySide2/CMakeLists.txt +++ b/sources/pyside2/PySide2/CMakeLists.txt @@ -40,12 +40,18 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/__init__.py" "${CMAKE_CURRENT_BINARY_DIR}/support/__init__.py" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/signature/__init__.py" "${CMAKE_CURRENT_BINARY_DIR}/support/signature/__init__.py" COPYONLY) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/signature/layout.py" + "${CMAKE_CURRENT_BINARY_DIR}/support/signature/layout.py" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/signature/loader.py" "${CMAKE_CURRENT_BINARY_DIR}/support/signature/loader.py" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/signature/mapping.py" "${CMAKE_CURRENT_BINARY_DIR}/support/signature/mapping.py" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/signature/parser.py" "${CMAKE_CURRENT_BINARY_DIR}/support/signature/parser.py" COPYONLY) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/signature/lib/__init__.py" + "${CMAKE_CURRENT_BINARY_DIR}/support/signature/lib/__init__.py" COPYONLY) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/signature/lib/enum_sig.py" + "${CMAKE_CURRENT_BINARY_DIR}/support/signature/lib/enum_sig.py" COPYONLY) if (PYTHON_VERSION_MAJOR EQUAL 3) else() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/support/signature/backport_inspect.py" diff --git a/sources/pyside2/PySide2/QtGui/typesystem_gui_common.xml b/sources/pyside2/PySide2/QtGui/typesystem_gui_common.xml index f67b5a306..32d586631 100644 --- a/sources/pyside2/PySide2/QtGui/typesystem_gui_common.xml +++ b/sources/pyside2/PySide2/QtGui/typesystem_gui_common.xml @@ -2694,10 +2694,10 @@ The signature "QList<qreal>" is needed by the __reduce__ methods, but created by some other object used elsewhere. - After the matrix type was changed, "QList<float>" was nowhere created. + After the matrix type was changed, "QList<float>" was created nowhere. I don't know an explicit way to produce the right conversion function, so what I did - was to create a dummy function and immediately dele it again. + was to create a dummy function and immediately delete it again. This has the desired effect of creating the implicitly needed "QList<float>" conversion, although the dummy function goes away. @@ -2983,10 +2983,12 @@ </extra-includes> </primitive-type> - <object-type name="QWindow"> + <object-type name="QWindow" delete-in-main-thread="true"> <enum-type name="AncestorMode"/> <enum-type name="Visibility"/> + <modify-function signature="raise()" rename="raise_" /> </object-type> + <!-- Qt5: not sure if this needs support, skipped for now --> <rejection class="QWindow" function-name="nativeEvent"/>" @@ -3013,7 +3015,6 @@ } </inject-code> </add-function> - <modify-function signature="exec()" rename="exec_" allow-thread="yes"/> <inject-code class="native" file="glue/qguiapplication_init.cpp" position="beginning" /> </object-type> diff --git a/sources/pyside2/PySide2/QtQuick/typesystem_quick.xml b/sources/pyside2/PySide2/QtQuick/typesystem_quick.xml index 7e6b450c9..dde90166f 100644 --- a/sources/pyside2/PySide2/QtQuick/typesystem_quick.xml +++ b/sources/pyside2/PySide2/QtQuick/typesystem_quick.xml @@ -65,7 +65,7 @@ <object-type name="QQuickImageResponse" since="5.6" /> <object-type name="QQuickTransform" /> - <object-type name="QQuickItem"> + <object-type name="QQuickItem" delete-in-main-thread="true"> <value-type name="UpdatePaintNodeData" /> <enum-type name="Flag" flags="Flags" /> <enum-type name="ItemChange" /> diff --git a/sources/pyside2/PySide2/QtWidgets/typesystem_widgets_common.xml b/sources/pyside2/PySide2/QtWidgets/typesystem_widgets_common.xml index 9c21493da..1f2cd446b 100644 --- a/sources/pyside2/PySide2/QtWidgets/typesystem_widgets_common.xml +++ b/sources/pyside2/PySide2/QtWidgets/typesystem_widgets_common.xml @@ -2196,7 +2196,7 @@ </modify-function> </object-type> - <object-type name="QWidget"> + <object-type name="QWidget" delete-in-main-thread="true"> <!-- Qt5: remove native event for now --> <modify-function signature="nativeEvent(const QByteArray &,void*,long*)" remove="all" /> diff --git a/sources/pyside2/PySide2/__init__.py.in b/sources/pyside2/PySide2/__init__.py.in index ab50ef776..631f5f13a 100644 --- a/sources/pyside2/PySide2/__init__.py.in +++ b/sources/pyside2/PySide2/__init__.py.in @@ -4,18 +4,22 @@ __all__ = list("Qt" + body for body in __version__ = "@FINAL_PACKAGE_VERSION@" __version_info__ = (@BINDING_API_MAJOR_VERSION@, @BINDING_API_MINOR_VERSION@, @BINDING_API_MICRO_VERSION@, "@BINDING_API_PRE_RELEASE_VERSION_TYPE@", "@BINDING_API_PRE_RELEASE_VERSION@") -@PYSIDE_BUILD_DATE@ -@PYSIDE_BUILD_COMMIT_DATE@ -@PYSIDE_BUILD_COMMIT_HASH@ -@PYSIDE_BUILD_COMMIT_HASH_DESCRIBED@ - -# Timestamp used for snapshot build, which is part of snapshot package version. -@PYSIDE_SETUP_PY_PACKAGE_TIMESTAMP_ASSIGNMENT@ - def _setupQtDirectories(): import sys import os + # On Windows we need to explicitly import the shiboken2 module so + # that the libshiboken.dll dependency is loaded by the time a + # Qt module is imported. Otherwise due to PATH not containing + # the shiboken2 module path, the Qt module import would fail + # due to the missing libshiboken dll. + # We need to do the same on Linux and macOS, because we do not + # embed rpaths into the PySide2 libraries that would point to + # the libshiboken library location. Importing the module + # loads the libraries into the process memory beforehand, and + # thus takes care of it for us. + import shiboken2 + pyside_package_dir = os.path.abspath(os.path.dirname(__file__)) # Used by signature module. os.environ["PYSIDE_PACKAGE_DIR"] = pyside_package_dir diff --git a/sources/pyside2/PySide2/_config.py.in b/sources/pyside2/PySide2/_config.py.in index 31a2f7a50..740e9a001 100644 --- a/sources/pyside2/PySide2/_config.py.in +++ b/sources/pyside2/PySide2/_config.py.in @@ -8,10 +8,9 @@ pyside_library_soversion = str(@PYSIDE_SO_VERSION@) version = "@FINAL_PACKAGE_VERSION@" version_info = (@BINDING_API_MAJOR_VERSION@, @BINDING_API_MINOR_VERSION@, @BINDING_API_MICRO_VERSION@, "@BINDING_API_PRE_RELEASE_VERSION_TYPE@", "@BINDING_API_PRE_RELEASE_VERSION@") -@PYSIDE_BUILD_DATE@ -@PYSIDE_BUILD_COMMIT_DATE@ -@PYSIDE_BUILD_COMMIT_HASH@ -@PYSIDE_BUILD_COMMIT_HASH_DESCRIBED@ - -# Timestamp used for snapshot build, which is part of snapshot package version. -@PYSIDE_SETUP_PY_PACKAGE_TIMESTAMP_ASSIGNMENT@ +@PACKAGE_BUILD_DATE@ +@PACKAGE_BUILD_COMMIT_DATE@ +@PACKAGE_BUILD_COMMIT_HASH@ +@PACKAGE_BUILD_COMMIT_HASH_DESCRIBED@ +@PACKAGE_SETUP_PY_PACKAGE_TIMESTAMP_ASSIGNMENT@ +@PACKAGE_SETUP_PY_PACKAGE_VERSION_ASSIGNMENT@ diff --git a/sources/pyside2/PySide2/support/signature/__init__.py b/sources/pyside2/PySide2/support/signature/__init__.py index 0ff9ec7e9..14e63a5fb 100644 --- a/sources/pyside2/PySide2/support/signature/__init__.py +++ b/sources/pyside2/PySide2/support/signature/__init__.py @@ -1,6 +1,6 @@ ############################################################################# ## -## Copyright (C) 2017 The Qt Company Ltd. +## Copyright (C) 2018 The Qt Company Ltd. ## Contact: https://www.qt.io/licensing/ ## ## This file is part of Qt for Python. @@ -40,3 +40,7 @@ from __future__ import print_function, absolute_import from .loader import inspect +from PySide2 import QtCore +if QtCore.QProcess.__signature__: + pass # trigger initialization +from signature_loader import get_signature diff --git a/sources/pyside2/PySide2/support/signature/fix-complaints.py b/sources/pyside2/PySide2/support/signature/fix-complaints.py index fa2b44420..e078ef1ab 100644 --- a/sources/pyside2/PySide2/support/signature/fix-complaints.py +++ b/sources/pyside2/PySide2/support/signature/fix-complaints.py @@ -1,6 +1,6 @@ ############################################################################# ## -## Copyright (C) 2017 The Qt Company Ltd. +## Copyright (C) 2018 The Qt Company Ltd. ## Contact: https://www.qt.io/licensing/ ## ## This file is part of Qt for Python. diff --git a/sources/pyside2/PySide2/support/signature/layout.py b/sources/pyside2/PySide2/support/signature/layout.py new file mode 100644 index 000000000..ac7833f03 --- /dev/null +++ b/sources/pyside2/PySide2/support/signature/layout.py @@ -0,0 +1,237 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +from __future__ import print_function, absolute_import + +""" +layout.py + +The signature module now has the capability to configure +differently formatted versions of signatures. The default +layout is known from the "__signature__" attribute. + +The function "get_signature(ob, modifier=None)" produces the same +signatures by default. By passing different modifiers, you +can select different layouts. + +This module configures the different layouts which can be used. +It also implements them in this file. The configurations are +used literally as strings like "signature", "existence", etc. +""" + +from textwrap import dedent +from .loader import inspect + +class SimpleNamespace(object): + # From types.rst, because the builtin is implemented in Python 3, only. + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + def __repr__(self): + keys = sorted(self.__dict__) + items = ("{}={!r}".format(k, self.__dict__[k]) for k in keys) + return "{}({})".format(type(self).__name__, ", ".join(items)) + + def __eq__(self, other): + return self.__dict__ == other.__dict__ + +class SignatureLayout(SimpleNamespace): + """ + Configure a signature. + + The layout of signatures can have different layouts which are + controlled by keyword arguments: + + definition=True Determines if self will generated. + defaults=True + ellipsis=False Replaces defaults by "...". + return_annotation=True + parameter_names=True False removes names before ":". + """ + allowed_keys = SimpleNamespace(definition=True, + defaults=True, + ellipsis=False, + return_annotation=True, + parameter_names=True) + allowed_values = True, False + + def __init__(self, **kwds): + args = SimpleNamespace(**self.allowed_keys.__dict__) + args.__dict__.update(kwds) + self.__dict__.update(args.__dict__) + err_keys = list(set(self.__dict__) - set(self.allowed_keys.__dict__)) + if err_keys: + self._attributeerror(err_keys) + err_values = list(set(self.__dict__.values()) - set(self.allowed_values)) + if err_values: + self._valueerror(err_values) + + def __setattr__(self, key, value): + if key not in self.allowed_keys.__dict__: + self._attributeerror([key]) + if value not in self.allowed_values: + self._valueerror([value]) + self.__dict__[key] = value + + def _attributeerror(self, err_keys): + err_keys = ", ".join(err_keys) + allowed_keys = ", ".join(self.allowed_keys.__dict__.keys()) + raise AttributeError(dedent("""\ + Not allowed: '{err_keys}'. + The only allowed keywords are '{allowed_keys}'. + """.format(**locals()))) + + def _valueerror(self, err_values): + err_values = ", ".join(map(str, err_values)) + allowed_values = ", ".join(map(str, self.allowed_values)) + raise ValueError(dedent("""\ + Not allowed: '{err_values}'. + The only allowed values are '{allowed_values}'. + """.format(**locals()))) + +# The following names are used literally in this module. +# This way, we avoid the dict hashing problem. +signature = SignatureLayout() + +existence = SignatureLayout(definition=False, + defaults=False, + return_annotation=False, + parameter_names=False) + +hintingstub = SignatureLayout(ellipsis=True) + +typeerror = SignatureLayout(definition=False, + return_annotation=False, + parameter_names=False) + +def define_nameless_parameter(): + """ + Create Nameless Parameters + + A nameless parameter has a reduced string representation. + This is done by cloning the parameter type and overwriting its + __str__ method. The inner structure is still a valid parameter. + """ + def __str__(self): + # for Python 2, we must change self to be an instance of P + klass = self.__class__ + self.__class__ = P + txt = P.__str__(self) + self.__class__ = klass + txt = txt[txt.index(":") + 1:].strip() if ":" in txt else txt + return txt + + P = inspect.Parameter + newname = "NamelessParameter" + bases = P.__bases__ + body = dict(P.__dict__) # get rid of mappingproxy + if "__slots__" in body: + # __slots__ would create duplicates + for name in body["__slots__"]: + del body[name] + body["__str__"] = __str__ + return type(newname, bases, body) + +NamelessParameter = define_nameless_parameter() + +def make_signature_nameless(signature): + """ + Make a Signature Nameless + + We use an existing signature and change the type of its parameters. + The signature looks different, but is totally intact. + """ + for key in signature.parameters.keys(): + Signature.parameters[key].__class__ = NamelessParameter + +def create_signature(props, key): + if not props: + # empty signatures string + return + if isinstance(props["multi"], list): + # multi sig: call recursively + return list(create_signature(elem, key) + for elem in props["multi"]) + if type(key) is tuple: + sig_kind, modifier = key + else: + sig_kind, modifier = key, "signature" + + layout = globals()[modifier] # lookup of the modifier, here + if not isinstance(layout, SignatureLayout): + raise SystemError("Modifiers must be names of a SignatureLayout " + "instance") + + # this is the basic layout of a signature + varnames = props["varnames"] + if layout.definition: + if sig_kind == "method": + varnames = ("self",) + varnames + elif sig_kind == "staticmethod": + pass + elif sig_kind == "classmethod": + varnames = ("klass",) + varnames + else: + raise SystemError("Methods must be normal, staticmethod or " + "classmethod") + # calculate the modifications + defaults = props["defaults"][:] + if not layout.defaults: + defaults = () + if layout.ellipsis: + defaults = ("...",) * len(defaults) + annotations = props["annotations"].copy() + if not layout.return_annotation and "return" in annotations: + del annotations["return"] + + # attach parameters to a fake function and build a signature + argstr = ", ".join(varnames) + fakefunc = eval("lambda {}: None".format(argstr)) + fakefunc.__name__ = props["name"] + fakefunc.__defaults__ = defaults + fakefunc.__kwdefaults__ = props["kwdefaults"] + fakefunc.__annotations__ = annotations + sig = inspect._signature_from_function(inspect.Signature, fakefunc) + + # the special case of nameless parameters + if not layout.parameter_names: + make_signature_nameless(sig) + return sig + +# end of file diff --git a/sources/pyside2/PySide2/support/signature/lib/__init__.py b/sources/pyside2/PySide2/support/signature/lib/__init__.py new file mode 100644 index 000000000..2d640cb89 --- /dev/null +++ b/sources/pyside2/PySide2/support/signature/lib/__init__.py @@ -0,0 +1,40 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +# this file intentionally left blank diff --git a/sources/pyside2/PySide2/support/signature/lib/enum_sig.py b/sources/pyside2/PySide2/support/signature/lib/enum_sig.py new file mode 100644 index 000000000..702ee7ebd --- /dev/null +++ b/sources/pyside2/PySide2/support/signature/lib/enum_sig.py @@ -0,0 +1,113 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +from PySide2.support.signature import inspect, get_signature + + +class ExactEnumerator(object): + """ + ExactEnumerator enumerates all signatures in a module as they are. + + This class is used for generating complete listings of all signatures. + An appropriate formatter should be supplied, if printable output + is desired. + """ + def __init__(self, formatter, result_type=dict): + self.fmt = formatter + self.result_type = result_type + + def module(self, mod_name): + __import__(mod_name) + with self.fmt.module(mod_name): + module = sys.modules[mod_name] + members = inspect.getmembers(module, inspect.isclass) + ret = self.result_type() + for class_name, klass in members: + ret.update(self.klass(class_name, klass)) + return ret + + def klass(self, class_name, klass): + bases_list = [] + for base in klass.__bases__: + name = base.__name__ + if name == "object": + pass + else: + modname = base.__module__ + name = modname + "." + base.__name__ + bases_list.append(name) + class_str = "{}({})".format(class_name, ", ".join(bases_list)) + with self.fmt.klass(class_name, class_str): + ret = self.function("__init__", klass) + # class_members = inspect.getmembers(klass) + # gives us also the inherited things. + class_members = sorted(list(klass.__dict__.items())) + for func_name, func in class_members: + ret.update(self.function(func_name, func)) + return ret + + def function(self, func_name, func): + ret = self.result_type() + signature = getattr(func, '__signature__', None) + if signature is not None: + with self.fmt.function(func_name, signature) as key: + ret[key] = signature + return ret + + +class SimplifyingEnumerator(ExactEnumerator): + """ + SimplifyingEnumerator enumerates all signatures in a module filtered. + + There are no default values, no variable + names and no self parameter. Only types are present after simplification. + The functions 'next' resp. '__next__' are removed + to make the output identical for Python 2 and 3. + An appropriate formatter should be supplied, if printable output + is desired. + """ + + def function(self, func_name, func): + ret = self.result_type() + signature = get_signature(func, 'existence') + if signature is not None and func_name not in ("next", "__next__"): + with self.fmt.function(func_name, signature) as key: + ret[key] = signature + return ret diff --git a/sources/pyside2/PySide2/support/signature/loader.py b/sources/pyside2/PySide2/support/signature/loader.py index f51bafe79..21ecebcc8 100644 --- a/sources/pyside2/PySide2/support/signature/loader.py +++ b/sources/pyside2/PySide2/support/signature/loader.py @@ -1,6 +1,6 @@ ############################################################################# ## -## Copyright (C) 2017 The Qt Company Ltd. +## Copyright (C) 2018 The Qt Company Ltd. ## Contact: https://www.qt.io/licensing/ ## ## This file is part of Qt for Python. @@ -80,31 +80,10 @@ from PySide2.support.signature.parser import pyside_type_init sys.path.pop(0) # Note also that during the tests we have a different encoding that would # break the Python license decorated files without an encoding line. +from PySide2.support.signature import layout # name used in signature.cpp -def create_signature(props, sig_kind): - if not props: - # empty signatures string - return - if isinstance(props["multi"], list): - return list(create_signature(elem, sig_kind) - for elem in props["multi"]) - varnames = props["varnames"] - if sig_kind == "method": - varnames = ("self",) + varnames - elif sig_kind == "staticmethod": - pass - elif sig_kind == "classmethod": - varnames = ("klass",) + varnames - else: - raise SystemError("Methods must be normal, staticmethod or " - "classmethod") - argstr = ", ".join(varnames) - fakefunc = eval("lambda {}: None".format(argstr)) - fakefunc.__name__ = props["name"] - fakefunc.__defaults__ = props["defaults"] - fakefunc.__kwdefaults__ = props["kwdefaults"] - fakefunc.__annotations__ = props["annotations"] - return inspect._signature_from_function(inspect.Signature, fakefunc) +def create_signature(props, key): + return layout.create_signature(props, key) # end of file diff --git a/sources/pyside2/PySide2/support/signature/mapping.py b/sources/pyside2/PySide2/support/signature/mapping.py index dd3df0988..6b7d1ad01 100644 --- a/sources/pyside2/PySide2/support/signature/mapping.py +++ b/sources/pyside2/PySide2/support/signature/mapping.py @@ -1,6 +1,6 @@ ############################################################################# ## -## Copyright (C) 2017 The Qt Company Ltd. +## Copyright (C) 2018 The Qt Company Ltd. ## Contact: https://www.qt.io/licensing/ ## ## This file is part of Qt for Python. @@ -46,7 +46,7 @@ This module has the mapping from the pyside C-modules view of signatures to the Python representation. The PySide modules are not loaded in advance, but only after they appear -in sys.modules. This minimises the loading overhead. +in sys.modules. This minimizes the loading overhead. In principle, we need to re-load the module, when the imports change. But it is much easier to do it on demand, when we get an exception. See _resolve_value() in singature.py @@ -71,7 +71,6 @@ FloatMatrix = typing.List[typing.List[float]] # Pair could be more specific, but we loose the info in the generator. Pair = typing.Tuple[typing.Any, typing.Any] MultiMap = typing.DefaultDict[str, typing.List[str]] -Text = typing.Text # ulong_max is only 32 bit on windows. ulong_max = 2*sys.maxsize+1 if len(struct.pack("L", 1)) != 4 else 0xffffffff @@ -153,7 +152,7 @@ type_map = {} def init_QtCore(): import PySide2.QtCore - from PySide2.QtCore import Qt, QUrl, QDir, QGenericArgument + from PySide2.QtCore import Qt, QUrl, QDir from PySide2.QtCore import QRect, QSize, QPoint, QLocale, QByteArray from PySide2.QtCore import QMarginsF # 5.9 try: @@ -201,9 +200,8 @@ def init_QtCore(): "ULONG_MAX": ulong_max, "quintptr": int, "PyCallable": typing.Callable, - "...": ellipsis, # no idea how this should be translated... maybe so? "PyTypeObject": type, - "PySequence": typing.Sequence, + "PySequence": typing.Iterable, # important for numpy "qptrdiff": int, "true": True, "Qt.HANDLE": int, # be more explicit with some consts? @@ -242,7 +240,7 @@ def init_QtCore(): "QDir.SortFlags(QDir.Name | QDir.IgnoreCase)"), "PyBytes": bytes, "PyByteArray": bytearray, - "PyUnicode": Text, + "PyUnicode": typing.Text, "signed long": int, "PySide2.QtCore.int": int, "PySide2.QtCore.char": StringList, # A 'char **' is a list of strings. @@ -259,13 +257,13 @@ def init_QtCore(): "float[][]": FloatMatrix, # 5.9 "PySide2.QtCore.unsigned int": int, # 5.9 Ubuntu "PySide2.QtCore.long long": int, # 5.9, MSVC 15 - "QGenericArgument(nullptr)": QGenericArgument(None), # 5.10 + "QGenericArgument(nullptr)": ellipsis, # 5.10 "QModelIndex()": Invalid("PySide2.QtCore.QModelIndex"), # repr is btw. very wrong, fix it?! - "QGenericArgument((0))": None, # 5.6, RHEL 6.6. Is that ok? - "QGenericArgument()": None, - "QGenericArgument(0)": None, - "QGenericArgument(NULL)": None, # 5.6, MSVC - "QGenericArgument(Q_NULLPTR)": None, + "QGenericArgument((0))": ellipsis, # 5.6, RHEL 6.6. Is that ok? + "QGenericArgument()": ellipsis, + "QGenericArgument(0)": ellipsis, + "QGenericArgument(NULL)": ellipsis, # 5.6, MSVC + "QGenericArgument(Q_NULLPTR)": ellipsis, "zero(PySide2.QtCore.QObject)": None, "zero(PySide2.QtCore.QThread)": None, "zero(quintptr)": 0, @@ -289,6 +287,8 @@ def init_QtCore(): "zero(PySide2.QtCore.QEvent.Type)": None, "CheckIndexOption.NoOption": Instance( "PySide2.QtCore.QAbstractItemModel.CheckIndexOptions.NoOption"), # 5.11 + "QVariantMap": dict, + "PySide2.QtCore.QCborStreamReader.StringResult": typing.AnyStr, }) try: type_map.update({ @@ -301,7 +301,6 @@ def init_QtCore(): def init_QtGui(): import PySide2.QtGui - from PySide2.QtGui import QPageLayout, QPageSize # 5.9 type_map.update({ "QVector< QTextLayout.FormatRange >()": [], # do we need more structure? "USHRT_MAX": ushort_max, @@ -313,7 +312,7 @@ def init_QtGui(): "GL_COLOR_BUFFER_BIT": GL_COLOR_BUFFER_BIT, "GL_NEAREST": GL_NEAREST, "WId": WId, - "PySide2.QtGui.QPlatformSurface": Virtual("PySide2.QtGui.QPlatformSurface"), # hmm... + "PySide2.QtGui.QPlatformSurface": int, # a handle "QList< QTouchEvent.TouchPoint >()": [], # XXX improve? "QPixmap()": Default("PySide2.QtGui.QPixmap"), # can't create without qApp "PySide2.QtCore.uint8_t": int, # macOS 5.9 @@ -325,6 +324,7 @@ def init_QtGui(): "zero(PySide2.QtGui.QTextLayout.FormatRange)": None, "zero(PySide2.QtGui.QTouchDevice)": None, "zero(PySide2.QtGui.QScreen)": None, + "PySide2.QtGui.QGenericMatrix": Missing("PySide2.QtGui.QGenericMatrix"), }) return locals() @@ -396,7 +396,6 @@ def init_QtMultimedia(): import PySide2.QtMultimedia import PySide2.QtMultimediaWidgets type_map.update({ - "QVariantMap": dict, "QGraphicsVideoItem": PySide2.QtMultimediaWidgets.QGraphicsVideoItem, "QVideoWidget": PySide2.QtMultimediaWidgets.QVideoWidget, }) diff --git a/sources/pyside2/PySide2/support/signature/parser.py b/sources/pyside2/PySide2/support/signature/parser.py index 9313fb540..dd6640fde 100644 --- a/sources/pyside2/PySide2/support/signature/parser.py +++ b/sources/pyside2/PySide2/support/signature/parser.py @@ -1,6 +1,6 @@ ############################################################################# ## -## Copyright (C) 2017 The Qt Company Ltd. +## Copyright (C) 2018 The Qt Company Ltd. ## Contact: https://www.qt.io/licensing/ ## ## This file is part of Qt for Python. @@ -48,6 +48,7 @@ import functools from .mapping import type_map, update_mapping, __dict__ as namespace _DEBUG = False +LIST_KEYWORDS = False """ parser.py @@ -119,6 +120,8 @@ def _parse_line(line): for arg in arglist: name, ann = arg.split(":") if name in keyword.kwlist: + if LIST_KEYWORDS: + print("KEYWORD", ret) name = name + "_" if "=" in ann: ann, default = ann.split("=") @@ -130,6 +133,10 @@ def _parse_line(line): multi = ret["multi"] if multi is not None: ret["multi"] = int(multi) + funcname = ret["funcname"] + parts = funcname.split(".") + if parts[-1] in keyword.kwlist: + ret["funcname"] = funcname + "_" return ret def make_good_value(thing, valtype): @@ -192,8 +199,14 @@ def calculate_props(line): arglist = res["arglist"] annotations = {} _defaults = [] - for tup in arglist: + for idx, tup in enumerate(arglist): name, ann = tup[:2] + if ann == "...": + name = "*args" + # copy the fields back :() + ann = 'NULL' # maps to None + tup = name, ann + arglist[idx] = tup annotations[name] = _resolve_type(ann, line) if len(tup) == 3: default = _resolve_value(tup[2], ann, line) @@ -214,6 +227,31 @@ def calculate_props(line): props["multi"] = res["multi"] return props +def fixup_multilines(sig_str): + lines = list(line.strip() for line in sig_str.strip().splitlines()) + res = [] + multi_lines = [] + for line in lines: + multi = re.match(r"([0-9]+):", line) + if multi: + idx, rest = int(multi.group(1)), line[multi.end():] + multi_lines.append(rest) + if idx > 0: + continue + # remove duplicates + multi_lines = list(set(multi_lines)) + # renumber or return a single line + nmulti = len(multi_lines) + if nmulti > 1: + for idx, line in enumerate(multi_lines): + res.append("{}:{}".format(nmulti-idx-1, line)) + else: + res.append(multi_lines[0]) + multi_lines = [] + else: + res.append(line) + return res + def pyside_type_init(typemod, sig_str): dprint() if type(typemod) is types.ModuleType: @@ -222,9 +260,10 @@ def pyside_type_init(typemod, sig_str): dprint("Initialization of type '{}.{}'".format(typemod.__module__, typemod.__name__)) update_mapping() + lines = fixup_multilines(sig_str) ret = {} multi_props = [] - for line in sig_str.strip().splitlines(): + for line in lines: props = calculate_props(line) shortname = props["name"] multi = props["multi"] @@ -232,10 +271,10 @@ def pyside_type_init(typemod, sig_str): ret[shortname] = props dprint(props) else: - fullname = props.pop("fullname") multi_props.append(props) if multi > 0: continue + fullname = props.pop("fullname") multi_props = {"multi": multi_props, "fullname": fullname} ret[shortname] = multi_props dprint(multi_props) diff --git a/sources/pyside2/tests/registry/init_platform.py b/sources/pyside2/tests/registry/init_platform.py index 03d0ed133..22875a63e 100644 --- a/sources/pyside2/tests/registry/init_platform.py +++ b/sources/pyside2/tests/registry/init_platform.py @@ -55,9 +55,10 @@ from textwrap import dedent all_modules = list("PySide2." + x for x in PySide2.__all__) -from PySide2.support.signature import inspect from PySide2.QtCore import __version__ +from PySide2.support.signature.lib.enum_sig import SimplifyingEnumerator + is_py3 = sys.version_info[0] == 3 is_ci = os.environ.get("QTEST_ENVIRONMENT", "") == "ci" # Python2 legacy: Correct 'linux2' to 'linux', recommended way. @@ -114,7 +115,7 @@ class Formatter(object): Formatter is formatting the signature listing of an enumerator. It is written as context managers in order to avoid many callbacks. - The division in formatter and enumerator is done to keep the + The separation in formatter and enumerator is done to keep the unrelated tasks of enumeration and formatting apart. """ def __init__(self, outfile): @@ -134,7 +135,7 @@ class Formatter(object): self.print(" })") @contextmanager - def klass(self, class_name): + def klass(self, class_name, class_str): self.class_name = class_name self.print() self.print(" # class {}.{}:".format(self.mod_name, class_name)) @@ -152,89 +153,6 @@ class Formatter(object): yield key -class ExactEnumerator(object): - """ - ExactEnumerator enumerates all signatures in a module as they are. - - This class is used for generating complete listings of all signatures. - An appropriate formatter should be supplied, if printable output - is desired. - """ - def __init__(self, formatter, result_type=dict): - self.fmt = formatter - self.result_type = result_type - - def module(self, mod_name): - __import__(mod_name) - with self.fmt.module(mod_name): - module = sys.modules[mod_name] - members = inspect.getmembers(module, inspect.isclass) - ret = self.result_type() - for class_name, klass in members: - ret.update(self.klass(class_name, klass)) - return ret - - def klass(self, class_name, klass): - with self.fmt.klass(class_name): - ret = self.function("__init__", klass) - # class_members = inspect.getmembers(klass) - # gives us also the inherited things. - class_members = sorted(list(klass.__dict__.items())) - for func_name, func in class_members: - ret.update(self.function(func_name, func)) - return ret - - def function(self, func_name, func): - ret = self.result_type() - signature = getattr(func, '__signature__', None) - if signature is not None: - with self.fmt.function(func_name, signature) as key: - ret[key] = signature - return ret - - -def simplify(signature): - if isinstance(signature, list): - # remove duplicates which still sometimes occour: - ret = set(simplify(sig) for sig in signature) - return sorted(ret) if len(ret) > 1 else list(ret)[0] - ret = [] - for pv in signature.parameters.values(): - txt = str(pv) - if txt == "self": - continue - txt = txt[txt.index(":") + 1:] - if "=" in txt: - txt = txt[:txt.index("=")] - quote = txt[0] - if quote in ("'", '"') and txt[-1] == quote: - txt = txt[1:-1] - ret.append(txt) - return tuple(ret) - - -class SimplifyingEnumerator(ExactEnumerator): - """ - SimplifyingEnumerator enumerates all signatures in a module filtered. - - There are no default values, no variable - names and no self parameter. Only types are present after simplification. - The functions 'next' resp. '__next__' are removed - to make the output identical for Python 2 and 3. - An appropriate formatter should be supplied, if printable output - is desired. - """ - - def function(self, func_name, func): - ret = self.result_type() - signature = getattr(func, '__signature__', None) - sig = simplify(signature) if signature is not None else None - if sig is not None and func_name not in ("next", "__next__"): - with self.fmt.function(func_name, sig) as key: - ret[key] = sig - return ret - - def enum_all(): fmt = Formatter(None) enu = SimplifyingEnumerator(fmt) diff --git a/sources/shiboken2/ApiExtractor/abstractmetalang.cpp b/sources/shiboken2/ApiExtractor/abstractmetalang.cpp index 35e12f780..c65d7e0bd 100644 --- a/sources/shiboken2/ApiExtractor/abstractmetalang.cpp +++ b/sources/shiboken2/ApiExtractor/abstractmetalang.cpp @@ -1613,6 +1613,13 @@ void AbstractMetaClass::setTemplateBaseClassInstantiations(AbstractMetaTypeList& metaClassBaseTemplateInstantiations()->insert(this, instantiations); } +// Does any of the base classes require deletion in the main thread? +bool AbstractMetaClass::deleteInMainThread() const +{ + return typeEntry()->deleteInMainThread() + || (m_baseClass && m_baseClass->deleteInMainThread()); +} + static bool functions_contains(const AbstractMetaFunctionList &l, const AbstractMetaFunction *func) { for (const AbstractMetaFunction *f : l) { diff --git a/sources/shiboken2/ApiExtractor/abstractmetalang.h b/sources/shiboken2/ApiExtractor/abstractmetalang.h index 42129e9b7..aaefa32d5 100644 --- a/sources/shiboken2/ApiExtractor/abstractmetalang.h +++ b/sources/shiboken2/ApiExtractor/abstractmetalang.h @@ -1699,6 +1699,8 @@ public: return m_hasToStringCapability; } + bool deleteInMainThread() const; + static AbstractMetaClass *findClass(const AbstractMetaClassList &classes, const QString &name); static AbstractMetaClass *findClass(const AbstractMetaClassList &classes, diff --git a/sources/shiboken2/ApiExtractor/typedatabase.cpp b/sources/shiboken2/ApiExtractor/typedatabase.cpp index 69cddca4c..dcfd9f740 100644 --- a/sources/shiboken2/ApiExtractor/typedatabase.cpp +++ b/sources/shiboken2/ApiExtractor/typedatabase.cpp @@ -782,6 +782,7 @@ void ComplexTypeEntry::formatDebug(QDebug &d) const FORMAT_BOOL("QObject", m_qobject) FORMAT_BOOL("polymorphicBase", m_polymorphicBase) FORMAT_BOOL("genericClass", m_genericClass) + FORMAT_BOOL("deleteInMainThread", m_deleteInMainThread) if (m_typeFlags != 0) d << ", typeFlags=" << m_typeFlags; d << ", copyableFlag=" << m_copyableFlag diff --git a/sources/shiboken2/ApiExtractor/typesystem.cpp b/sources/shiboken2/ApiExtractor/typesystem.cpp index ba219cf5f..21c35bda6 100644 --- a/sources/shiboken2/ApiExtractor/typesystem.cpp +++ b/sources/shiboken2/ApiExtractor/typesystem.cpp @@ -1301,8 +1301,8 @@ void Handler::applyComplexTypeAttributes(const QXmlStreamReader &reader, if (convertBoolean(attributes->takeAt(i).value(), deprecatedAttribute(), false)) ctype->setTypeFlags(ctype->typeFlags() | ComplexTypeEntry::Deprecated); } else if (name == deleteInMainThreadAttribute()) { - qCWarning(lcShiboken, "%s", - qPrintable(msgUnimplementedAttributeWarning(reader, name))); + if (convertBoolean(attributes->takeAt(i).value(), deleteInMainThreadAttribute(), false)) + ctype->setDeleteInMainThread(true); } else if (name == QLatin1String("target-type")) { ctype->setTargetType(attributes->takeAt(i).value().toString()); } @@ -3221,7 +3221,8 @@ ComplexTypeEntry::ComplexTypeEntry(const QString &name, TypeEntry::Type t, m_qualifiedCppName(name), m_qobject(false), m_polymorphicBase(false), - m_genericClass(false) + m_genericClass(false), + m_deleteInMainThread(false) { } diff --git a/sources/shiboken2/ApiExtractor/typesystem.h b/sources/shiboken2/ApiExtractor/typesystem.h index 028f016f3..721d19f29 100644 --- a/sources/shiboken2/ApiExtractor/typesystem.h +++ b/sources/shiboken2/ApiExtractor/typesystem.h @@ -1346,6 +1346,9 @@ public: m_genericClass = isGeneric; } + bool deleteInMainThread() const { return m_deleteInMainThread; } + void setDeleteInMainThread(bool d) { m_deleteInMainThread = d; } + CopyableFlag copyable() const { return m_copyableFlag; @@ -1403,6 +1406,7 @@ private: uint m_qobject : 1; uint m_polymorphicBase : 1; uint m_genericClass : 1; + uint m_deleteInMainThread : 1; QString m_polymorphicIdValue; QString m_lookupName; diff --git a/sources/shiboken2/CMakeLists.txt b/sources/shiboken2/CMakeLists.txt index 1af84fca1..12a9b8773 100644 --- a/sources/shiboken2/CMakeLists.txt +++ b/sources/shiboken2/CMakeLists.txt @@ -5,8 +5,9 @@ include(CheckIncludeFileCXX) cmake_minimum_required(VERSION 3.1) cmake_policy(VERSION 3.1) -set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/Modules/ +set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/../cmake_helpers/ ${CMAKE_MODULE_PATH}) +include(helpers) find_package(Qt5 5.7 REQUIRED COMPONENTS Core Xml XmlPatterns) @@ -158,6 +159,8 @@ list(GET SHIBOKEN_VERSION_OUTPUT 4 shiboken_PRE_RELEASE_VERSION) set(shiboken2_VERSION "${shiboken_MAJOR_VERSION}.${shiboken_MINOR_VERSION}.${shiboken_MICRO_VERSION}") set(shiboken2_library_so_version "${shiboken_MAJOR_VERSION}.${shiboken_MINOR_VERSION}") +compute_config_py_values(shiboken2_VERSION) + ## For debugging the PYTHON* variables message("PYTHONLIBS_FOUND: " ${PYTHONLIBS_FOUND}) message("PYTHON_LIBRARIES: " ${PYTHON_LIBRARIES}) diff --git a/sources/shiboken2/generator/CMakeLists.txt b/sources/shiboken2/generator/CMakeLists.txt index 032118666..fb8058b2d 100644 --- a/sources/shiboken2/generator/CMakeLists.txt +++ b/sources/shiboken2/generator/CMakeLists.txt @@ -38,3 +38,33 @@ target_link_libraries(shiboken2 configure_file(shibokenconfig.h.in "${CMAKE_CURRENT_BINARY_DIR}/shibokenconfig.h" @ONLY) install(TARGETS shiboken2 DESTINATION bin) + +set(shiboken_generator_package_name "shiboken2_generator") + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/_config.py.in" + "${CMAKE_CURRENT_BINARY_DIR}/_config.py" @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/_config.py" + DESTINATION "${PYTHON_SITE_PACKAGES}/${shiboken_generator_package_name}") + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/__init__.py.in" + "${CMAKE_CURRENT_BINARY_DIR}/__init__.py" @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/__init__.py" + DESTINATION "${PYTHON_SITE_PACKAGES}/${shiboken_generator_package_name}") + +# shiboken2 setuptools entry point +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/../shiboken_tool.py + DESTINATION bin + PERMISSIONS + OWNER_EXECUTE OWNER_WRITE OWNER_READ + GROUP_EXECUTE GROUP_READ + WORLD_EXECUTE WORLD_READ) + +# Use absolute path instead of relative path, to avoid ninja build errors due to +# duplicate file dependency inconsistency. +set(shiboken_version_relative_path "${CMAKE_CURRENT_SOURCE_DIR}/../shiboken_version.py") +get_filename_component(shiboken_version_path ${shiboken_version_relative_path} ABSOLUTE) +configure_file("${shiboken_version_path}" + "${CMAKE_CURRENT_BINARY_DIR}/_git_shiboken_generator_version.py" @ONLY) + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/_git_shiboken_generator_version.py" + DESTINATION "${PYTHON_SITE_PACKAGES}/${shiboken_generator_package_name}") diff --git a/sources/shiboken2/generator/__init__.py.in b/sources/shiboken2/generator/__init__.py.in new file mode 100644 index 000000000..4be6a833b --- /dev/null +++ b/sources/shiboken2/generator/__init__.py.in @@ -0,0 +1,2 @@ +__version__ = "@FINAL_PACKAGE_VERSION@" +__version_info__ = (@shiboken_MAJOR_VERSION@, @shiboken_MINOR_VERSION@, @shiboken_MICRO_VERSION@, "@shiboken_PRE_RELEASE_VERSION_TYPE@", "@shiboken_PRE_RELEASE_VERSION@") diff --git a/sources/shiboken2/generator/_config.py.in b/sources/shiboken2/generator/_config.py.in new file mode 100644 index 000000000..985735fa4 --- /dev/null +++ b/sources/shiboken2/generator/_config.py.in @@ -0,0 +1,9 @@ +version = "@FINAL_PACKAGE_VERSION@" +version_info = (@shiboken_MAJOR_VERSION@, @shiboken_MINOR_VERSION@, @shiboken_MICRO_VERSION@, "@shiboken_PRE_RELEASE_VERSION_TYPE@", "@shiboken_PRE_RELEASE_VERSION@") + +@PACKAGE_BUILD_DATE@ +@PACKAGE_BUILD_COMMIT_DATE@ +@PACKAGE_BUILD_COMMIT_HASH@ +@PACKAGE_BUILD_COMMIT_HASH_DESCRIBED@ +@PACKAGE_SETUP_PY_PACKAGE_TIMESTAMP_ASSIGNMENT@ +@PACKAGE_SETUP_PY_PACKAGE_VERSION_ASSIGNMENT@ diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp index 0f94793e9..f0d6c9082 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp @@ -502,7 +502,7 @@ void CppGenerator::generateClass(QTextStream &s, GeneratorContext &classContext) if (metaClass->typeEntry()->isValue() || metaClass->typeEntry()->isSmartPointer()) { writeCopyFunction(s, classContext); - signatureStream << metaClass->fullName() << ".__copy__()" << endl; + signatureStream << fullPythonClassName(metaClass) << ".__copy__()" << endl; } // Write single method definitions @@ -4947,8 +4947,16 @@ void CppGenerator::writeClassRegister(QTextStream &s, else s << INDENT << "0," << endl; - // 9:isInnerClass - s << INDENT << (hasEnclosingClass ? "true" : "false") << endl; + // 9:wrapperflags + QByteArrayList wrapperFlags; + if (hasEnclosingClass) + wrapperFlags.append(QByteArrayLiteral("Shiboken::ObjectType::WrapperFlags::InnerClass")); + if (metaClass->deleteInMainThread()) + wrapperFlags.append(QByteArrayLiteral("Shiboken::ObjectType::WrapperFlags::DeleteInMainThread")); + if (wrapperFlags.isEmpty()) + s << INDENT << '0'; + else + s << INDENT << wrapperFlags.join(" | "); } s << INDENT << ");" << endl; s << INDENT << endl; diff --git a/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp b/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp index 6d263dd01..b9eea7529 100644 --- a/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp @@ -361,7 +361,19 @@ QString ShibokenGenerator::wrapperName(const AbstractMetaType *metaType) const return metaType->cppSignature(); } -QString ShibokenGenerator::fullPythonFunctionName(const AbstractMetaFunction* func) +QString ShibokenGenerator::fullPythonClassName(const AbstractMetaClass *metaClass) +{ + QString fullClassName = metaClass->name(); + const AbstractMetaClass *enclosing = metaClass->enclosingClass(); + while (enclosing) { + fullClassName.prepend(enclosing->name() + QLatin1Char('.')); + enclosing = enclosing->enclosingClass(); + } + fullClassName.prepend(packageName() + QLatin1Char('.')); + return fullClassName; +} + +QString ShibokenGenerator::fullPythonFunctionName(const AbstractMetaFunction *func) //WS { QString funcName; if (func->isOperatorOverload()) @@ -369,11 +381,11 @@ QString ShibokenGenerator::fullPythonFunctionName(const AbstractMetaFunction* fu else funcName = func->name(); if (func->ownerClass()) { - QString fullName = func->ownerClass()->fullName(); + QString fullClassName = fullPythonClassName(func->ownerClass()); if (func->isConstructor()) - funcName = fullName; + funcName = fullClassName; else - funcName.prepend(fullName + QLatin1Char('.')); + funcName.prepend(fullClassName + QLatin1Char('.')); } else { funcName = packageName() + QLatin1Char('.') + func->name(); diff --git a/sources/shiboken2/generator/shiboken2/shibokengenerator.h b/sources/shiboken2/generator/shiboken2/shibokengenerator.h index 76f71827a..60e31a99b 100644 --- a/sources/shiboken2/generator/shiboken2/shibokengenerator.h +++ b/sources/shiboken2/generator/shiboken2/shibokengenerator.h @@ -214,7 +214,8 @@ protected: QString wrapperName(const AbstractMetaClass* metaClass) const; QString wrapperName(const AbstractMetaType *metaType) const; - QString fullPythonFunctionName(const AbstractMetaFunction* func); + QString fullPythonClassName(const AbstractMetaClass *metaClass); + QString fullPythonFunctionName(const AbstractMetaFunction *func); //WS static QString protectedEnumSurrogateName(const AbstractMetaEnum* metaEnum); static QString protectedFieldGetterName(const AbstractMetaField* field); diff --git a/sources/shiboken2/libshiboken/basewrapper.cpp b/sources/shiboken2/libshiboken/basewrapper.cpp index 2b27f2881..2f5f27989 100644 --- a/sources/shiboken2/libshiboken/basewrapper.cpp +++ b/sources/shiboken2/libshiboken/basewrapper.cpp @@ -40,6 +40,7 @@ #include "basewrapper.h" #include "basewrapper_p.h" #include "bindingmanager.h" +#include "helper.h" #include "sbkconverter.h" #include "sbkenum.h" #include "sbkstring.h" @@ -190,6 +191,12 @@ SbkObjectType *SbkObject_TypeF(void) return reinterpret_cast<SbkObjectType *>(type); } +static int mainThreadDeletionHandler(void *) +{ + if (Py_IsInitialized()) + Shiboken::BindingManager::instance().runDeletionInMainThread(); + return 0; +} static void SbkDeallocWrapperCommon(PyObject* pyObj, bool canDelete) { @@ -218,8 +225,27 @@ static void SbkDeallocWrapperCommon(PyObject* pyObj, bool canDelete) PyObject_ClearWeakRefs(pyObj); // If I have ownership and is valid delete C++ pointer - if (canDelete && sbkObj->d->hasOwnership && sbkObj->d->validCppObject) { - SbkObjectTypePrivate *sotp = PepType_SOTP(pyType); + SbkObjectTypePrivate *sotp{nullptr}; + canDelete &= sbkObj->d->hasOwnership && sbkObj->d->validCppObject; + if (canDelete) { + sotp = PepType_SOTP(pyType); + if (sotp->delete_in_main_thread && Shiboken::currentThreadId() != Shiboken::mainThreadId()) { + auto &bindingManager = Shiboken::BindingManager::instance(); + if (sotp->is_multicpp) { + Shiboken::DtorAccumulatorVisitor visitor(sbkObj); + Shiboken::walkThroughClassHierarchy(Py_TYPE(pyObj), &visitor); + for (const auto &e : visitor.entries()) + bindingManager.addToDeletionInMainThread(e); + } else { + Shiboken::DestructorEntry e{sotp->cpp_dtor, sbkObj->d->cptr[0]}; + bindingManager.addToDeletionInMainThread(e); + } + Py_AddPendingCall(mainThreadDeletionHandler, nullptr); + canDelete = false; + } + } + + if (canDelete) { if (sotp->is_multicpp) { Shiboken::DtorAccumulatorVisitor visitor(sbkObj); Shiboken::walkThroughClassHierarchy(Py_TYPE(pyObj), &visitor); @@ -533,6 +559,8 @@ bool DtorAccumulatorVisitor::visit(SbkObjectType *node) return false; } +void _initMainThreadId(); // helper.cpp + namespace Conversions { void init(); } void init() @@ -541,6 +569,8 @@ void init() if (shibokenAlreadInitialised) return; + _initMainThreadId(); + Conversions::init(); PyEval_InitThreads(); @@ -735,7 +765,7 @@ introduceWrapperType(PyObject *enclosingObject, ObjectDestructor cppObjDtor, SbkObjectType *baseType, PyObject *baseTypes, - bool isInnerClass) + unsigned wrapperFlags) { if (baseType) { typeSpec->slots[0].pfunc = reinterpret_cast<void *>(baseType); @@ -760,10 +790,14 @@ introduceWrapperType(PyObject *enclosingObject, return nullptr; initPrivateData(type); + auto sotp = PepType_SOTP(type); + if (wrapperFlags & DeleteInMainThread) + sotp->delete_in_main_thread = 1; + setOriginalName(type, originalName); setDestructorFunction(type, cppObjDtor); - if (isInnerClass) { + if (wrapperFlags & InnerClass) { if (PyDict_SetItemString(enclosingObject, typeName, reinterpret_cast<PyObject *>(type)) == 0) return type; else diff --git a/sources/shiboken2/libshiboken/basewrapper.h b/sources/shiboken2/libshiboken/basewrapper.h index 06b17a151..f8940b842 100644 --- a/sources/shiboken2/libshiboken/basewrapper.h +++ b/sources/shiboken2/libshiboken/basewrapper.h @@ -192,6 +192,12 @@ LIBSHIBOKEN_API void setDestructorFunction(SbkObjectType* self, ObjectDes LIBSHIBOKEN_API void initPrivateData(SbkObjectType* self); +enum WrapperFlags +{ + InnerClass = 0x1, + DeleteInMainThread = 0x2 +}; + /** * Initializes a Shiboken wrapper type and adds it to the module, * or to the enclosing class if the type is an inner class. @@ -217,7 +223,7 @@ LIBSHIBOKEN_API SbkObjectType *introduceWrapperType(PyObject *enclosingObject, ObjectDestructor cppObjDtor, SbkObjectType *baseType, PyObject *baseTypes, - bool isInnerClass); + unsigned wrapperFlags = 0); /** * Set the subtype init hook for a type. diff --git a/sources/shiboken2/libshiboken/basewrapper_p.h b/sources/shiboken2/libshiboken/basewrapper_p.h index d99ca19ea..f8a381078 100644 --- a/sources/shiboken2/libshiboken/basewrapper_p.h +++ b/sources/shiboken2/libshiboken/basewrapper_p.h @@ -137,6 +137,7 @@ struct SbkObjectTypePrivate /// Tells is the type is a value type or an object-type, see BEHAVIOUR_* constants. // TODO-CONVERTERS: to be deprecated/removed int type_behaviour : 2; + int delete_in_main_thread : 1; /// C++ name char* original_name; /// Type user data diff --git a/sources/shiboken2/libshiboken/bindingmanager.cpp b/sources/shiboken2/libshiboken/bindingmanager.cpp index 82c5bd65f..8a9e912fd 100644 --- a/sources/shiboken2/libshiboken/bindingmanager.cpp +++ b/sources/shiboken2/libshiboken/bindingmanager.cpp @@ -136,8 +136,11 @@ static void showWrapperMap(const WrapperMap& wrapperMap) #endif struct BindingManager::BindingManagerPrivate { + using DestructorEntries = std::vector<DestructorEntry>; + WrapperMap wrapperMapper; Graph classHierarchy; + DestructorEntries deleteInMainThread; bool destroying; BindingManagerPrivate() : destroying(false) {} @@ -249,6 +252,18 @@ void BindingManager::releaseWrapper(SbkObject* sbkObj) sbkObj->d->validCppObject = false; } +void BindingManager::runDeletionInMainThread() +{ + for (const DestructorEntry &e : m_d->deleteInMainThread) + e.destructor(e.cppInstance); + m_d->deleteInMainThread.clear(); +} + +void BindingManager::addToDeletionInMainThread(const DestructorEntry &e) +{ + m_d->deleteInMainThread.push_back(e); +} + SbkObject* BindingManager::retrieveWrapper(const void* cptr) { WrapperMap::iterator iter = m_d->wrapperMapper.find(cptr); diff --git a/sources/shiboken2/libshiboken/bindingmanager.h b/sources/shiboken2/libshiboken/bindingmanager.h index c09b985dc..d03aa999a 100644 --- a/sources/shiboken2/libshiboken/bindingmanager.h +++ b/sources/shiboken2/libshiboken/bindingmanager.h @@ -50,6 +50,8 @@ struct SbkObjectType; namespace Shiboken { +struct DestructorEntry; + typedef void (*ObjectVisitor)(SbkObject*, void*); class LIBSHIBOKEN_API BindingManager @@ -67,6 +69,9 @@ public: void registerWrapper(SbkObject* pyObj, void* cptr); void releaseWrapper(SbkObject* wrapper); + void runDeletionInMainThread(); + void addToDeletionInMainThread(const DestructorEntry &); + SbkObject* retrieveWrapper(const void* cptr); PyObject* getOverride(const void* cptr, const char* methodName); diff --git a/sources/shiboken2/libshiboken/helper.cpp b/sources/shiboken2/libshiboken/helper.cpp index 472924723..e42daff07 100644 --- a/sources/shiboken2/libshiboken/helper.cpp +++ b/sources/shiboken2/libshiboken/helper.cpp @@ -41,6 +41,12 @@ #include "sbkstring.h" #include <stdarg.h> +#ifdef _WIN32 +# include <windows.h> +#else +# include <pthread.h> +#endif + namespace Shiboken { @@ -141,4 +147,24 @@ int warning(PyObject* category, int stacklevel, const char* format, ...) return result; } +ThreadId currentThreadId() +{ +#if defined(_WIN32) + return GetCurrentThreadId(); +#elif defined(__APPLE_CC__) + return reinterpret_cast<ThreadId>(pthread_self()); +#else + return pthread_self(); +#endif +} + +// Internal, used by init() from main thread +static ThreadId _mainThreadId{0}; +void _initMainThreadId() { _mainThreadId = currentThreadId(); } + +ThreadId mainThreadId() +{ + return _mainThreadId; +} + } // namespace Shiboken diff --git a/sources/shiboken2/libshiboken/helper.h b/sources/shiboken2/libshiboken/helper.h index 79a30871a..9b6d8903f 100644 --- a/sources/shiboken2/libshiboken/helper.h +++ b/sources/shiboken2/libshiboken/helper.h @@ -90,6 +90,10 @@ class AutoArrayPointer T* data; }; +typedef unsigned long long ThreadId; +LIBSHIBOKEN_API ThreadId currentThreadId(); +LIBSHIBOKEN_API ThreadId mainThreadId(); + /** * An utility function used to call PyErr_WarnEx with a formatted message. */ diff --git a/sources/shiboken2/libshiboken/pep384impl.cpp b/sources/shiboken2/libshiboken/pep384impl.cpp index 4481f1cdd..18094c5c0 100644 --- a/sources/shiboken2/libshiboken/pep384impl.cpp +++ b/sources/shiboken2/libshiboken/pep384impl.cpp @@ -398,7 +398,10 @@ PyRun_String(const char *str, int start, PyObject *globals, PyObject *locals) return ret; } +#endif // Py_LIMITED_API + // This is only a simple local helper that returns a computed variable. +// Used also in Python 2. static PyObject * PepRun_GetResult(const char *command, const char *resvar) { @@ -415,6 +418,8 @@ PepRun_GetResult(const char *command, const char *resvar) return res; } +#ifdef Py_LIMITED_API + /***************************************************************************** * * Support for classobject.h @@ -499,13 +504,26 @@ PyTypeObject *PepStaticMethod_TypePtr = NULL; static PyTypeObject *getStaticMethodType(void) { + // this works for Python 3, only + // "StaticMethodType = type(str.__dict__['maketrans'])\n"; static const char prog[] = - "StaticMethodType = type(str.__dict__['maketrans'])\n"; - return (PyTypeObject *) PepRun_GetResult(prog, "StaticMethodType"); + "from xxsubtype import spamlist\n" + "StaticMethod_Type = type(spamlist.__dict__['staticmeth'])\n"; + return (PyTypeObject *) PepRun_GetResult(prog, "StaticMethod_Type"); } - #endif // Py_LIMITED_API +#if PY_VERSION_HEX < 0x03000000 +PyTypeObject *PepMethodDescr_TypePtr = NULL; + +static PyTypeObject *getMethodDescrType(void) +{ + static const char prog[] = + "MethodDescr_Type = type(str.split)\n"; + return (PyTypeObject *) PepRun_GetResult(prog, "MethodDescr_Type"); +} +#endif + /***************************************************************************** * * Common newly needed functions @@ -630,6 +648,9 @@ Pep384_Init() PepFunction_TypePtr = getFunctionType(); PepStaticMethod_TypePtr = getStaticMethodType(); #endif +#if PY_VERSION_HEX < 0x03000000 + PepMethodDescr_TypePtr = getMethodDescrType(); +#endif } } // extern "C" diff --git a/sources/shiboken2/libshiboken/pep384impl.h b/sources/shiboken2/libshiboken/pep384impl.h index b566a6218..6649fa95e 100644 --- a/sources/shiboken2/libshiboken/pep384impl.h +++ b/sources/shiboken2/libshiboken/pep384impl.h @@ -286,7 +286,7 @@ LIBSHIBOKEN_API PyObject *PyRun_String(const char *, int, PyObject *, PyObject * // But this is no problem as we check it's validity for every version. #define PYTHON_BUFFER_VERSION_COMPATIBLE (PY_VERSION_HEX >= 0x03030000 && \ - PY_VERSION_HEX < 0X0307FFFF) + PY_VERSION_HEX < 0x0307FFFF) #if !PYTHON_BUFFER_VERSION_COMPATIBLE # error Please check the buffer compatibility for this python version! #endif @@ -470,6 +470,12 @@ extern LIBSHIBOKEN_API PyTypeObject *PepStaticMethod_TypePtr; #else #define PepStaticMethod_TypePtr &PyStaticMethod_Type #endif +// Although not PEP specific, we resolve this similar issue, here: +#if PY_VERSION_HEX < 0x03000000 +extern LIBSHIBOKEN_API PyTypeObject *PepMethodDescr_TypePtr; +#else +#define PepMethodDescr_TypePtr &PyMethodDescr_Type +#endif /***************************************************************************** * diff --git a/sources/shiboken2/libshiboken/signature.cpp b/sources/shiboken2/libshiboken/signature.cpp index f0bb8e609..24d60acc4 100644 --- a/sources/shiboken2/libshiboken/signature.cpp +++ b/sources/shiboken2/libshiboken/signature.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt for Python. @@ -77,18 +77,23 @@ typedef struct safe_globals_struc { static safe_globals pyside_globals = 0; -static PyObject *GetSignature_Function(PyCFunctionObject *); -static PyObject *GetSignature_TypeMod(PyObject *); +static PyObject *GetSignature_Function(PyCFunctionObject *, const char *); +static PyObject *GetSignature_TypeMod(PyObject *, const char *); +static PyObject *GetSignature_Wrapper(PyObject *, const char *); +static PyObject *get_signature(PyObject *self, PyObject *args); static PyObject *PySide_BuildSignatureProps(PyObject *class_mod); +static void init_module_1(void); +static void init_module_2(void); + const char helper_module_name[] = "signature_loader"; const char bootstrap_name[] = "bootstrap"; const char arg_name[] = "pyside_arg_dict"; const char func_name[] = "pyside_type_init"; static PyObject * -CreateSignature(PyObject *props, const char *sig_kind) +CreateSignature(PyObject *props, PyObject *key) { /* * Here is the new function to create all signatures. It simply calls @@ -97,22 +102,22 @@ CreateSignature(PyObject *props, const char *sig_kind) * to support '_signature_is_functionlike()'. */ return PyObject_CallFunction(pyside_globals->createsig_func, - (char *)"(Os)", props, sig_kind); + (char *)"(OO)", props, key); } static PyObject * -pyside_cf_get___signature__(PyObject *func) +pyside_cf_get___signature__(PyObject *func, const char *modifier) { - return GetSignature_Function((PyCFunctionObject *)func); + return GetSignature_Function((PyCFunctionObject *)func, modifier); } static PyObject * -pyside_sm_get___signature__(PyObject *sm) +pyside_sm_get___signature__(PyObject *sm, const char *modifier) { PyObject *func, *ret; func = PyObject_GetAttrString(sm, "__func__"); - ret = GetSignature_Function((PyCFunctionObject *)func); + ret = GetSignature_Function((PyCFunctionObject *)func, modifier); Py_XDECREF(func); return ret; } @@ -192,7 +197,7 @@ qualname_to_func(PyObject *ob) #endif static PyObject * -pyside_md_get___signature__(PyObject *ob) +pyside_md_get___signature__(PyObject *ob, const char *modifier) { PyObject *func; PyObject *result; @@ -218,21 +223,31 @@ pyside_md_get___signature__(PyObject *ob) return Py_None; if (func == NULL) Py_FatalError("missing mapping in MethodDescriptor"); - result = pyside_cf_get___signature__(func); + result = pyside_cf_get___signature__(func, modifier); Py_DECREF(func); return result; } static PyObject * -pyside_tp_get___signature__(PyObject *typemod) +pyside_wd_get___signature__(PyObject *ob, const char *modifier) +{ + return GetSignature_Wrapper(ob, modifier); +} + +static PyObject * +pyside_tp_get___signature__(PyObject *typemod, const char *modifier) { - return GetSignature_TypeMod(typemod); + return GetSignature_TypeMod(typemod, modifier); } +// forward +static PyObject * +GetSignature_Cached(PyObject *props, const char *sig_kind, const char *modifier); + static PyObject * -GetSignature_Function(PyCFunctionObject *func) +GetSignature_Function(PyCFunctionObject *func, const char *modifier) { - PyObject *typemod, *type_name, *dict, *props, *value, *selftype; + PyObject *typemod, *type_name, *dict, *props, *selftype; PyObject *func_name = PyObject_GetAttrString((PyObject *)func, "__name__"); const char *sig_kind; int flags; @@ -241,12 +256,8 @@ GetSignature_Function(PyCFunctionObject *func) if (selftype == NULL) selftype = PyDict_GetItem(pyside_globals->map_dict, (PyObject *)func); if (selftype == NULL) { - if (!PyErr_Occurred()) { - PyErr_Format(PyExc_SystemError, - "the signature for \"%s\" should exist", - PepCFunction_GET_NAMESTR(func) - ); - } + if (!PyErr_Occurred()) + Py_RETURN_NONE; return NULL; } if ((PyType_Check(selftype) || PyModule_Check(selftype))) @@ -279,24 +290,46 @@ GetSignature_Function(PyCFunctionObject *func) sig_kind = "staticmethod"; else sig_kind = "method"; - value = PyDict_GetItemString(props, sig_kind); - if (value == NULL) { - // we need to compute a signature object - value = CreateSignature(props, sig_kind); - if (value != NULL) { - if (PyDict_SetItemString(props, sig_kind, value) < 0) - return NULL; - } - else + return GetSignature_Cached(props, sig_kind, modifier); +} + +static PyObject * +GetSignature_Wrapper(PyObject *ob, const char *modifier) +{ + PyObject *dict, *props; + PyObject *func_name = PyObject_GetAttrString(ob, "__name__"); + PyObject *objclass = PyObject_GetAttrString(ob, "__objclass__"); + PyObject *class_name = PyObject_GetAttrString(objclass, "__name__"); + const char *sig_kind; + + if (func_name == nullptr || objclass == nullptr || class_name == nullptr) + return nullptr; + dict = PyDict_GetItem(pyside_globals->arg_dict, class_name); + if (dict == NULL) + Py_RETURN_NONE; + if (PyTuple_Check(dict)) { + /* + * We do the initialization lazily. + * This has also the advantage that we can freely import PySide. + */ + dict = PySide_BuildSignatureProps(objclass); + if (dict == NULL) Py_RETURN_NONE; } - return Py_INCREF(value), value; + props = PyDict_GetItem(dict, func_name); + Py_DECREF(func_name); + Py_DECREF(objclass); + Py_DECREF(class_name); + if (props == NULL) + Py_RETURN_NONE; + sig_kind = "method"; + return GetSignature_Cached(props, sig_kind, modifier); } static PyObject * -GetSignature_TypeMod(PyObject *ob) +GetSignature_TypeMod(PyObject *ob, const char *modifier) { - PyObject *ob_name, *dict, *props, *value; + PyObject *ob_name, *dict, *props; const char *sig_kind; ob_name = PyObject_GetAttrString(ob, "__name__"); @@ -314,37 +347,62 @@ GetSignature_TypeMod(PyObject *ob) if (props == NULL) Py_RETURN_NONE; sig_kind = "method"; - value = PyDict_GetItemString(props, sig_kind); - if (value == NULL) { + return GetSignature_Cached(props, sig_kind, modifier); +} + +static PyObject * +GetSignature_Cached(PyObject *props, const char *sig_kind, const char *modifier) +{ + PyObject *key, *value; + + if (modifier == nullptr) + key = Py_BuildValue("s", sig_kind); + else + key = Py_BuildValue("(ss)", sig_kind, modifier); + if (key == nullptr) + return nullptr; + value = PyDict_GetItem(props, key); + if (value == nullptr) { // we need to compute a signature object - value = CreateSignature(props, sig_kind); - if (value != NULL) { - if (PyDict_SetItemString(props, sig_kind, value) < 0) - return NULL; + value = CreateSignature(props, key); + if (value != nullptr) { + if (PyDict_SetItem(props, key, value) < 0) { + // this is an error + Py_DECREF(key); + return nullptr; + } } - else + else { + // key not found + Py_DECREF(key); Py_RETURN_NONE; + } } return Py_INCREF(value), value; } - static const char PySide_PythonCode[] = - "from __future__ import print_function, absolute_import\n" - "import sys, os, traceback\n" - - "pyside_package_dir = os.environ.get('PYSIDE_PACKAGE_DIR', '.')\n" - "__file__ = os.path.join(pyside_package_dir, 'support', 'signature', 'loader.py')\n" - - "def bootstrap():\n" - " try:\n" - " with open(__file__) as _f:\n" - " exec(compile(_f.read(), __file__, 'exec'))\n" - " except Exception as e:\n" - " print('Exception:', e)\n" - " traceback.print_exc(file=sys.stdout)\n" - " globals().update(locals())\n" - ; + "from __future__ import print_function, absolute_import\n" R"~(if True: + + import sys, os, traceback + + pyside_package_dir = os.environ.get('PYSIDE_PACKAGE_DIR') + if pyside_package_dir is None: + # This happens in shiboken running ctest. + from distutils.sysconfig import get_python_lib + pyside_package_dir = os.path.join(get_python_lib(), 'PySide2') + __file__ = os.path.join(pyside_package_dir, 'support', 'signature', 'loader.py') + + def bootstrap(): + try: + with open(__file__) as _f: + exec(compile(_f.read(), __file__, 'exec')) + except Exception as e: + print('Exception:', e) + traceback.print_exc(file=sys.stdout) + globals().update(locals()) + + )~"; static safe_globals_struc * init_phase_1(void) @@ -387,9 +445,10 @@ error: } static int -init_phase_2(safe_globals_struc *p) +init_phase_2(safe_globals_struc *p, PyMethodDef *methods) { - PyObject *bootstrap_func; + PyObject *bootstrap_func, *v = nullptr; + PyMethodDef *ml; bootstrap_func = PyObject_GetAttrString(p->helper_module, bootstrap_name); if (bootstrap_func == NULL) @@ -403,9 +462,22 @@ init_phase_2(safe_globals_struc *p) p->createsig_func = PyObject_GetAttrString(p->helper_module, "create_signature"); if (p->createsig_func == NULL) goto error; + + // The single function to be called, but maybe more to come. + for (ml = methods; ml->ml_name != NULL; ml++) { + v = PyCFunction_NewEx(ml, nullptr, nullptr); + if (v == nullptr) { + goto error; + } + if (PyObject_SetAttrString(p->helper_module, ml->ml_name, v) != 0) { + goto error; + } + Py_DECREF(v); + } return 0; error: + Py_XDECREF(v); PyErr_SetString(PyExc_SystemError, "could not initialize part 2"); return -1; } @@ -413,8 +485,11 @@ error: static int add_more_getsets(PyTypeObject *type, PyGetSetDef *gsp) { - PyObject *dict = type->tp_dict; + PyObject *dict; + assert(PyType_Check(type)); + PyType_Ready(type); + dict = type->tp_dict; for (; gsp->name != NULL; gsp++) { PyObject *descr; if (PyDict_GetItemString(dict, gsp->name)) @@ -463,6 +538,45 @@ static PyGetSetDef new_PyType_getsets[] = { {0} }; +static PyGetSetDef new_PyWrapperDescr_getsets[] = { + {(char *) "__signature__", (getter)pyside_wd_get___signature__}, + {0} +}; + +//////////////////////////////////////////////////////////////////////////// +// +// get_signature -- providing a superior interface +// +// Additionally to the interface via __signature__, we also provide +// a general function, which allows for different signature layouts. +// The "modifier" argument is a string that is passed in from loader.py . +// Configuration what the modifiers mean is completely in Python. +// + +static PyObject * +get_signature(PyObject *self, PyObject *args) +{ + PyObject *ob; + const char *modifier = nullptr; + + init_module_1(); + init_module_2(); + + if (!PyArg_ParseTuple(args, "O|s", &ob, &modifier)) + return NULL; + if (Py_TYPE(ob) == &PyCFunction_Type) + return pyside_cf_get___signature__(ob, modifier); + if (Py_TYPE(ob) == PepStaticMethod_TypePtr) + return pyside_sm_get___signature__(ob, modifier); + if (Py_TYPE(ob) == PepMethodDescr_TypePtr) + return pyside_md_get___signature__(ob, modifier); + if (PyType_Check(ob)) + return pyside_tp_get___signature__(ob, modifier); + if (Py_TYPE(ob) == &PyWrapperDescr_Type) + return pyside_wd_get___signature__(ob, modifier); + Py_RETURN_NONE; +} + //////////////////////////////////////////////////////////////////////////// // // This special Type_Ready does certain initializations earlier with @@ -497,23 +611,23 @@ void handler(int sig) { static int PySideType_Ready(PyTypeObject *type) { - PyObject *md; + PyObject *md, *wd; static int init_done = 0; if (!init_done) { - // Python2 does not expose certain types. We look them up: - // PyMethodDescr_Type 'type(str.__dict__["split"])' - // PyClassMethodDescr_Type. 'type(dict.__dict__["fromkeys"])' - // The latter is not needed until we use class methods in PySide. - md = PyObject_GetAttrString((PyObject *)&PyString_Type, "split"); - if (md == NULL + md = PyObject_GetAttrString((PyObject *)&PyString_Type, "split"); // method-descriptor + wd = PyObject_GetAttrString((PyObject *)Py_TYPE(Py_True), "__add__"); // wrapper-descriptor + if (md == nullptr || wd == nullptr || PyType_Ready(Py_TYPE(md)) < 0 - || add_more_getsets(Py_TYPE(md), new_PyMethodDescr_getsets) < 0 + || add_more_getsets(PepMethodDescr_TypePtr, new_PyMethodDescr_getsets) < 0 || add_more_getsets(&PyCFunction_Type, new_PyCFunction_getsets) < 0 || add_more_getsets(PepStaticMethod_TypePtr, new_PyStaticMethod_getsets) < 0 - || add_more_getsets(&PyType_Type, new_PyType_getsets) < 0) + || add_more_getsets(&PyType_Type, new_PyType_getsets) < 0 + || add_more_getsets(Py_TYPE(wd), new_PyWrapperDescr_getsets) < 0 + ) return -1; Py_DECREF(md); + Py_DECREF(wd); #ifndef _WIN32 // We enable the stack trace in CI, only. const char *testEnv = getenv("QTEST_ENVIRONMENT"); @@ -550,20 +664,26 @@ build_func_to_type(PyObject *obtype) return 0; } +static void +init_module_1(void) +{ + static int init_done = 0; + + if (!init_done) { + pyside_globals = init_phase_1(); + if (pyside_globals != nullptr) + init_done = 1; + } +} + static int PySide_BuildSignatureArgs(PyObject *module, PyObject *type, const char *signatures) { PyObject *type_name, *arg_tup; const char *name = NULL; - static int init_done = 0; - if (!init_done) { - pyside_globals = init_phase_1(); - if (pyside_globals == NULL) - return -1; - init_done = 1; - } + init_module_1();; arg_tup = Py_BuildValue("(Os)", type, signatures); if (arg_tup == NULL) return -1; @@ -599,23 +719,34 @@ PySide_BuildSignatureArgs(PyObject *module, PyObject *type, return 0; } -static PyObject * -PySide_BuildSignatureProps(PyObject *classmod) +static PyMethodDef signature_methods[] = { + {"get_signature", (PyCFunction)get_signature, METH_VARARGS, + "get the __signature__, but pass an optional string parameter"}, + {NULL, NULL} +}; + +static void +init_module_2(void) { - PyObject *arg_tup, *dict, *type_name; static int init_done = 0; if (!init_done) { - if (init_phase_2(pyside_globals) < 0) - return NULL; + init_phase_2(pyside_globals, signature_methods); init_done = 1; } +} + +static PyObject * +PySide_BuildSignatureProps(PyObject *classmod) +{ + PyObject *arg_tup, *dict, *type_name; /* * Here is the second part of the function. * This part will be called on-demand when needed by some attribute. * We simply pick up the arguments that we stored here and replace * them by the function result. */ + init_module_2(); type_name = PyObject_GetAttrString(classmod, "__name__"); if (type_name == NULL) return NULL; diff --git a/sources/shiboken2/libshiboken/signature.h b/sources/shiboken2/libshiboken/signature.h index 3a229cb5c..b65317662 100644 --- a/sources/shiboken2/libshiboken/signature.h +++ b/sources/shiboken2/libshiboken/signature.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt for Python. @@ -45,8 +45,8 @@ extern "C" { -LIBSHIBOKEN_API int SbkSpecial_Type_Ready(PyObject *, PyTypeObject *, const char*); -LIBSHIBOKEN_API void FinishSignatureInitialization(PyObject *, const char*); +LIBSHIBOKEN_API int SbkSpecial_Type_Ready(PyObject *, PyTypeObject *, const char *); //WS +LIBSHIBOKEN_API void FinishSignatureInitialization(PyObject *, const char *); } // extern "C" diff --git a/sources/shiboken2/libshiboken/typespec.cpp b/sources/shiboken2/libshiboken/typespec.cpp index 9123a09e0..a67daf12d 100644 --- a/sources/shiboken2/libshiboken/typespec.cpp +++ b/sources/shiboken2/libshiboken/typespec.cpp @@ -37,6 +37,7 @@ ** ****************************************************************************/ +#include "sbkpython.h" #include "typespec.h" #include <structmember.h> diff --git a/sources/shiboken2/shiboken_tool.py b/sources/shiboken2/shiboken_tool.py new file mode 100755 index 000000000..8494c5d57 --- /dev/null +++ b/sources/shiboken2/shiboken_tool.py @@ -0,0 +1,53 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# +import sys +import os +import subprocess + +def main(): + # The tools listed as entrypoints in setup.py are copied to 'scripts/..' + cmd = os.path.join("..", os.path.basename(sys.argv[0])) + command = [os.path.join(os.path.dirname(os.path.realpath(__file__)), cmd)] + command.extend(sys.argv[1:]) + sys.exit(subprocess.call(command)) + +if __name__ == "__main__": + main() diff --git a/sources/shiboken2/shibokenmodule/CMakeLists.txt b/sources/shiboken2/shibokenmodule/CMakeLists.txt index f2d7b30f2..517aecba8 100644 --- a/sources/shiboken2/shibokenmodule/CMakeLists.txt +++ b/sources/shiboken2/shibokenmodule/CMakeLists.txt @@ -40,4 +40,24 @@ target_link_libraries(shibokenmodule add_dependencies(shibokenmodule shiboken2) -install(TARGETS shibokenmodule DESTINATION ${PYTHON_SITE_PACKAGES}) +install(TARGETS shibokenmodule DESTINATION ${PYTHON_SITE_PACKAGES}/shiboken2) + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/_config.py.in" + "${CMAKE_CURRENT_BINARY_DIR}/_config.py" @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/_config.py" + DESTINATION "${PYTHON_SITE_PACKAGES}/shiboken2") + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/__init__.py.in" + "${CMAKE_CURRENT_BINARY_DIR}/__init__.py" @ONLY) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/__init__.py" + DESTINATION "${PYTHON_SITE_PACKAGES}/shiboken2") + +# Use absolute path instead of relative path, to avoid ninja build errors due to +# duplicate file dependency inconsistency. +set(shiboken_version_relative_path "${CMAKE_CURRENT_SOURCE_DIR}/../shiboken_version.py") +get_filename_component(shiboken_version_path ${shiboken_version_relative_path} ABSOLUTE) +configure_file("${shiboken_version_path}" + "${CMAKE_CURRENT_BINARY_DIR}/_git_shiboken_module_version.py" @ONLY) + +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/_git_shiboken_module_version.py" + DESTINATION "${PYTHON_SITE_PACKAGES}/shiboken2") diff --git a/sources/shiboken2/shibokenmodule/__init__.py.in b/sources/shiboken2/shibokenmodule/__init__.py.in new file mode 100644 index 000000000..81ab0063a --- /dev/null +++ b/sources/shiboken2/shibokenmodule/__init__.py.in @@ -0,0 +1,4 @@ +__version__ = "@FINAL_PACKAGE_VERSION@" +__version_info__ = (@shiboken_MAJOR_VERSION@, @shiboken_MINOR_VERSION@, @shiboken_MICRO_VERSION@, "@shiboken_PRE_RELEASE_VERSION_TYPE@", "@shiboken_PRE_RELEASE_VERSION@") + +from .shiboken2 import * diff --git a/sources/shiboken2/shibokenmodule/_config.py.in b/sources/shiboken2/shibokenmodule/_config.py.in new file mode 100644 index 000000000..9607e5ca7 --- /dev/null +++ b/sources/shiboken2/shibokenmodule/_config.py.in @@ -0,0 +1,11 @@ +shiboken_library_soversion = str(@shiboken2_library_so_version@) + +version = "@FINAL_PACKAGE_VERSION@" +version_info = (@shiboken_MAJOR_VERSION@, @shiboken_MINOR_VERSION@, @shiboken_MICRO_VERSION@, "@shiboken_PRE_RELEASE_VERSION_TYPE@", "@shiboken_PRE_RELEASE_VERSION@") + +@PACKAGE_BUILD_DATE@ +@PACKAGE_BUILD_COMMIT_DATE@ +@PACKAGE_BUILD_COMMIT_HASH@ +@PACKAGE_BUILD_COMMIT_HASH_DESCRIBED@ +@PACKAGE_SETUP_PY_PACKAGE_TIMESTAMP_ASSIGNMENT@ +@PACKAGE_SETUP_PY_PACKAGE_VERSION_ASSIGNMENT@ diff --git a/testing/wheel_tester.py b/testing/wheel_tester.py new file mode 100644 index 000000000..60fd7a38a --- /dev/null +++ b/testing/wheel_tester.py @@ -0,0 +1,295 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +""" +This script is used by Coin (coin_test_instructions.py specifically) to +test installation of generated wheels, and test building of the +"buildable" examples samplebinding and scriptableapplication. + +It can also be invoked regularly from the command line via +python testing/wheel_tester.py --qmake=some-value --cmake=some-value + +The qmake and cmake arguments can also be omitted, and they will be +looked up in your PATH. + +Make sure that some generated wheels already exist in the dist/ +directory (e.g. setup.py bdist_wheel was already executed). +""" + +import os, sys + +try: + this_file = __file__ +except NameError: + this_file = sys.argv[0] +this_file = os.path.abspath(this_file) +this_dir = os.path.dirname(this_file) +setup_script_dir = os.path.abspath(os.path.join(this_dir, '..')) +sys.path.append(setup_script_dir) + +from build_scripts.options import OPTION_QMAKE +from build_scripts.options import OPTION_CMAKE + +from build_scripts.utils import find_files_using_glob +from build_scripts.utils import find_glob_in_path +from build_scripts.utils import run_process +from build_scripts.utils import rmtree +import distutils.log as log + +log.set_verbosity(1) + + +def find_executable_qmake(): + return find_executable('qmake', OPTION_QMAKE) + + +def find_executable_cmake(): + return find_executable('cmake', OPTION_CMAKE) + + +def find_executable(executable, command_line_value): + value = command_line_value + option_str = '--{}'.format(executable) + + if value: + log.info("{} option given: {}".format(option_str, value)) + if not os.path.exists(value): + raise RuntimeError("No executable exists at: {}".format(value)) + else: + log.info("No {} option given, trying to find {} in PATH.".format(option_str, executable)) + paths = find_glob_in_path(executable) + log.info("{} executables found in PATH: {}".format(executable, paths)) + if not paths: + raise RuntimeError( + "No {} option was specified and no {} was found " + "in PATH.".format(option_str, executable)) + else: + value = paths[0] + log.info("Using {} found in PATH: {}".format(executable, value)) + log.info("") + return value + + +QMAKE_PATH = find_executable_qmake() +CMAKE_PATH = find_executable_cmake() + + +def get_wheels_dir(): + return os.path.join(setup_script_dir, "dist") + + +def get_examples_dir(): + return os.path.join(setup_script_dir, "examples") + + +def package_prefix_names(): + return ["shiboken2", "shiboken2_generator", "PySide2"] + + +def clean_egg_info(): + # After a successful bdist_wheel build, some .egg-info directories + # are left over, which confuse pip when invoking it via + # python -m pip, making pip think that the packages are already + # installed in the root source directory. + # Clean up the .egg-info directories to fix this, it should be + # safe to do so. + paths = find_files_using_glob(setup_script_dir, "*.egg-info") + for p in paths: + log.info("Removing {}".format(p)) + rmtree(p) + + +def install_wheel(wheel_path): + log.info("Installing wheel: {}".format(wheel_path)) + exit_code = run_process([sys.executable, "-m", "pip", "install", wheel_path]) + log.info("") + if exit_code: + raise RuntimeError("Error while installing wheel {}".format(wheel_path)) + + +def try_install_wheels(wheels_dir, py_version): + clean_egg_info() + all_wheels_pattern = "*.whl" + all_wheels = find_files_using_glob(wheels_dir, all_wheels_pattern) + + if len(all_wheels) > 1: + log.info("Found the following wheels in {}: ".format(wheels_dir)) + for wheel in all_wheels: + log.info(wheel) + else: + log.info("No wheels found in {}".format(wheels_dir)) + log.info("") + + for p in package_prefix_names(): + pattern = "{}-*cp{}*.whl".format(p, py_version) + files = find_files_using_glob(wheels_dir, pattern) + if files and len(files) == 1: + wheel_path = files[0] + install_wheel(wheel_path) + elif len(files) > 1: + raise RuntimeError("More than one wheel found for specific package and version.") + else: + raise RuntimeError("No wheels compatible with Python {} found " + "for testing.".format(py_version)) + + +def is_unix(): + if sys.platform.startswith("linux") or sys.platform == "darwin": + return True + return False + + +def generate_build_cmake(): + args = [CMAKE_PATH] + if is_unix(): + args.extend(["-G", "Unix Makefiles"]) + else: + args.extend(["-G", "NMake Makefiles"]) + args.append("-DCMAKE_BUILD_TYPE=Release") + args.append("-Dpython_interpreter={}".format(sys.executable)) + + # Specify prefix path so find_package(Qt5) works. + qmake_dir = os.path.abspath(os.path.join(os.path.dirname(QMAKE_PATH), "..")) + args.append("-DCMAKE_PREFIX_PATH={}".format(qmake_dir)) + + args.append("..") + + exit_code = run_process(args) + if exit_code: + raise RuntimeError("Failure while running cmake.") + log.info("") + + +def generate_build_qmake(): + exit_code = run_process([QMAKE_PATH, "..", "python_interpreter={}".format(sys.executable)]) + if exit_code: + raise RuntimeError("Failure while running qmake.") + log.info("") + + +def run_make(): + args = [] + if is_unix(): + executable = "make" + else: + executable = "nmake" + args.append(executable) + + exit_code = run_process(args) + if exit_code: + raise RuntimeError("Failure while running {}.".format(executable)) + log.info("") + + +def run_make_install(): + args = [] + if is_unix(): + executable = "make" + else: + executable = "nmake" + args.append(executable) + args.append("install") + + exit_code = run_process(args) + if exit_code: + raise RuntimeError("Failed while running {} install.".format(executable)) + log.info("") + + +def execute_script(script_path): + args = [sys.executable, script_path] + exit_code = run_process(args) + if exit_code: + raise RuntimeError("Failure while executing script: {}".format(script_path)) + log.info("") + + +def prepare_build_folder(src_path, build_folder_name): + build_path = os.path.join(src_path, build_folder_name) + + # The script can be called for both Python 2 and Python 3 wheels, so + # preparing a build folder should clean any previous existing build. + if os.path.exists(build_path): + log.info("Removing {}".format(build_path)) + rmtree(build_path) + + log.info("Creating {}".format(build_path)) + os.makedirs(build_path) + os.chdir(build_path) + + +def try_build_examples(): + examples_dir = get_examples_dir() + + log.info("Attempting to build and run samplebinding using cmake.") + src_path = os.path.join(examples_dir, "samplebinding") + prepare_build_folder(src_path, "cmake") + generate_build_cmake() + run_make() + run_make_install() + execute_script(os.path.join(src_path, "main.py")) + + log.info("Attempting to build scriptableapplication using cmake.") + src_path = os.path.join(examples_dir, "scriptableapplication") + prepare_build_folder(src_path, "cmake") + generate_build_cmake() + run_make() + + log.info("Attempting to build scriptableapplication using qmake.") + src_path = os.path.join(examples_dir, "scriptableapplication") + prepare_build_folder(src_path, "qmake") + generate_build_qmake() + run_make() + + +def run_wheel_tests(): + wheels_dir = get_wheels_dir() + py_version = sys.version_info[0] + + log.info("Attempting to install wheels.\n") + try_install_wheels(wheels_dir, py_version) + + log.info("Attempting to build examples.\n") + try_build_examples() + + log.info("All tests passed!") + + +if __name__ == "__main__": + run_wheel_tests() |