diff options
author | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2018-03-06 07:58:51 +0100 |
---|---|---|
committer | Friedemann Kleint <Friedemann.Kleint@qt.io> | 2018-03-06 12:43:06 +0100 |
commit | df7c72e63c6b7aecee156db2eff726bcfb89977e (patch) | |
tree | b9cecf87e2cd75d11813cd06ef1f2a5ab28384de | |
parent | e621f81115cec5089f30755e09b9a59ece39660c (diff) | |
parent | f3139399b273fdf405f20cba32a08262cd933d6e (diff) |
Merge remote-tracking branch 'origin/5.9' into dev
Change-Id: I071f33063db6bf1175b017d65ac77bc95fe518df
31 files changed, 1049 insertions, 500 deletions
diff --git a/missing_bindings.py b/missing_bindings.py index 1b7b42441..894fdbd58 100644 --- a/missing_bindings.py +++ b/missing_bindings.py @@ -201,7 +201,9 @@ types_to_ignore.add('QSqlDriverPlugin') qt_documentation_website_prefixes = OrderedDict() qt_documentation_website_prefixes['5.6'] = 'http://doc.qt.io/qt-5.6/' qt_documentation_website_prefixes['5.8'] = 'http://doc.qt.io/qt-5.8/' -qt_documentation_website_prefixes['5.9'] = 'http://doc-snapshots.qt.io/qt5-5.9/' +qt_documentation_website_prefixes['5.9'] = 'http://doc.qt.io/qt-5.9/' +qt_documentation_website_prefixes['5.10'] = 'http://doc.qt.io/qt-5.10/' +qt_documentation_website_prefixes['5.11'] = 'http://doc-snapshots.qt.io/qt5-5.11/' qt_documentation_website_prefixes['dev'] = 'http://doc-snapshots.qt.io/qt5-dev/' @@ -37,25 +37,29 @@ ## ############################################################################# -import os, sys -import subprocess +import os, sys, re, subprocess from distutils.spawn import find_executable class QtInfo(object): def __init__(self, qmake_command=None): + self.initialized = False + if qmake_command: self._qmake_command = qmake_command else: self._qmake_command = [find_executable("qmake"),] - self._dict = {} - # bind all variables early at __init__ time. - for thing in self.__class__.__dict__: - getattr(self, thing) + + # Dict to cache qmake values. + self._query_dict = {} + # Dict to cache mkspecs variables. + self._mkspecs_dict = {} + # Initialize the properties. + self._initProperties() def getQMakeCommand(self): qmake_command_string = self._qmake_command[0] for entry in self._qmake_command[1:]: - qmake_command_string += " %s" %(entry) + qmake_command_string += " {}".format(entry) return qmake_command_string def getVersion(self): @@ -91,21 +95,127 @@ class QtInfo(object): def getQmlPath(self): return self.getProperty("QT_INSTALL_QML") - def _getProperty(self, prop_name): - cmd = self._qmake_command + ["-query", prop_name] + def getMacOSMinDeploymentTarget(self): + """ Return value is a macOS version or None. """ + return self.getProperty("QMAKE_MACOSX_DEPLOYMENT_TARGET") + + def getBuildType(self): + """ Return value is either debug, release, debug_release, or None. """ + return self.getProperty("BUILD_TYPE") + + def getSrcDir(self): + """ Return path to Qt src dir or None.. """ + return self.getProperty("QT_INSTALL_PREFIX/src") + + def getProperty(self, prop_name): + if prop_name not in self._query_dict: + return None + return self._query_dict[prop_name] + + def getProperties(self): + return self._query_dict + + def getMkspecsVariables(self): + return self._mkspecs_dict + + def _getQMakeOutput(self, args_list = []): + cmd = self._qmake_command + args_list proc = subprocess.Popen(cmd, stdout = subprocess.PIPE, shell=False) - prop = proc.communicate()[0] + output = proc.communicate()[0] proc.wait() if proc.returncode != 0: return None if sys.version_info >= (3,): - return str(prop, 'ascii').strip() - return prop.strip() + output = str(output, 'ascii').strip() + else: + output = output.strip() + return output + + def _parseQueryProperties(self, process_output): + props = {} + if not process_output: + return props + lines = [s.strip() for s in process_output.splitlines()] + for line in lines: + if line and ':' in line: + key, value = line.split(':', 1) + props[key] = value + return props + + def _getQueryProperties(self): + output = self._getQMakeOutput(['-query']) + self._query_dict = self._parseQueryProperties(output) + + def _parseQtBuildType(self): + key = 'QT_CONFIG' + if key not in self._mkspecs_dict: + return None - def getProperty(self, prop_name): - if prop_name not in self._dict: - self._dict[prop_name] = self._getProperty(prop_name) - return self._dict[prop_name] + qt_config = self._mkspecs_dict[key] + if 'debug_and_release' in qt_config: + return 'debug_and_release' + + split = qt_config.split(' ') + if 'release' in split and 'debug' in split: + return 'debug_and_release' + + if 'release' in split: + return 'release' + + if 'debug' in split: + return 'debug' + + return None + + def _getOtherProperties(self): + # Get the src property separately, because it is not returned by qmake unless explicitly + # specified. + key = 'QT_INSTALL_PREFIX/src' + result = self._getQMakeOutput(['-query', key]) + self._query_dict[key] = result + + # Get mkspecs variables and cache them. + self._getQMakeMkspecsVariables() + + # Get macOS minimum deployment target. + key = 'QMAKE_MACOSX_DEPLOYMENT_TARGET' + if key in self._mkspecs_dict: + self._query_dict[key] = self._mkspecs_dict[key] + + # Figure out how Qt was built: debug mode, release mode, or both. + build_type = self._parseQtBuildType() + if build_type: + self._query_dict['BUILD_TYPE'] = build_type + + def _initProperties(self): + self._getQueryProperties() + self._getOtherProperties() + + def _getQMakeMkspecsVariables(self): + # Create empty temporary qmake project file. + temp_file_name = 'qmake_fake_empty_project.txt' + open(temp_file_name, 'a').close() + + # Query qmake for all of its mkspecs variables. + qmakeOutput = self._getQMakeOutput(['-E', temp_file_name]) + lines = [s.strip() for s in qmakeOutput.splitlines()] + pattern = re.compile(r"^(.+?)=(.+?)$") + for line in lines: + found = pattern.search(line) + if found: + key = found.group(1).strip() + value = found.group(2).strip() + self._mkspecs_dict[key] = value + + # We need to clean up after qmake, which always creates a .qmake.stash file after a -E + # invocation. + qmake_stash_file = os.path.join(os.getcwd(), ".qmake.stash") + if os.path.exists(qmake_stash_file): + os.remove(qmake_stash_file) + + # Also clean up the temporary empty project file. + if os.path.exists(temp_file_name): + os.remove(temp_file_name) version = property(getVersion) bins_dir = property(getBinsPath) @@ -119,3 +229,6 @@ class QtInfo(object): headers_dir = property(getHeadersPath) docs_dir = property(getDocsPath) qml_dir = property(getQmlPath) + macos_min_deployment_target = property(getMacOSMinDeploymentTarget) + build_type = property(getBuildType) + src_dir = property(getSrcDir) @@ -82,6 +82,7 @@ For development purposes the following options might be of use, when using "setu --skip-packaging will skip creation of the python package, --ignore-git will skip the fetching and checkout steps for supermodule and all submodules. --verbose-build will output the compiler invocation with command line arguments, etc. + --sanitize-address will build all targets with address sanitizer enabled. REQUIREMENTS: - Python: 2.6, 2.7, 3.3, 3.4, 3.5 and 3.6 are supported @@ -109,78 +110,60 @@ OS X SDK: You can specify which OS X SDK should be used for compilation with the For e.g. "--osx-sysroot=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/". OS X Minimum deployment target: - You can specify the OS X minimum deployment target with the --osx-deployment-target=<x> option. - For example "--osx-deployment-target=10.10". + You can specify a custom OS X minimum deployment target with the --osx-deployment-target=<value> + option. + For example: "--osx-deployment-target=10.10". + + If the option is not set, the minimum deployment target of the used Qt library will be used + instead. Thus it is not necessary to use the option without a good reason. + If a new value is specified, it has to be higher or equal to both Python's and Qt's minimum + deployment targets. + + Description: + OS X allows specifying a minimum OS version on which a binary will be able to run. This implies + that an application can be built on a machine with the latest OS X version installed, with + latest Xcode version and SDK version and the built application can still run on an older OS + version. +""" - OS X provides the ability to set what is the minimum OS version on which a binary will run. This - means that a build can be done on the latest OS X version with latest XCode and SDK versions, - but the built application / library can run on older OS versions. +import os +import time +from utils import memoize, has_option, get_python_dict +OPTION_SNAPSHOT_BUILD = has_option("snapshot-build") +script_dir = os.getcwd() - Note: if the option is not set, CMake will try to query the MACOSX_DEPLOYMENT_TARGET environment - variable, and if that is empty, it will try to deduce a value internally (afaik based on - current OS X version and on the chosen SDK version). -""" +@memoize +def get_package_timestamp(): + return int(time.time()) -__version__ = "2.0.0.dev0" +@memoize +def get_package_version(): + """ Returns the version string for the PySide2 package. """ + pyside_version_py = os.path.join(script_dir, "sources", "pyside2", "pyside_version.py") + d = get_python_dict(pyside_version_py) -containedModules = ['shiboken2', 'pyside2'] + final_version = "{}.{}.{}".format(d['major_version'], d['minor_version'], d['patch_version']) + pre_release_version_type = d['pre_release_version_type'] + pre_release_version = d['pre_release_version'] + if pre_release_version and pre_release_version: + final_version += pre_release_version_type + pre_release_version -submodules = { - '2.0.0.dev0': [ - ["pyside2-tools", "dev"] - ], - '5.9': [ - ["pyside2-tools", "5.9"] - ], - '5.11': [ - ["pyside2-tools", "5.11"] - ], - '5.6': [ - ["pyside2-tools", "5.6"], - ["pyside2-examples", "5.6"], - ["wiki", "master", ".."] - ], -} -old_submodules = { - # these are just kept a while for reference but not maintained. - # if you need an old version, please use the pyside/pyside-setup version. - '1.3.0dev': [ - ["shiboken", "master"], - ["pyside", "master"], - ["pyside-tools", "master"], - ["pyside-examples", "master"], - ], - '1.2.2': [ - ["shiboken", "1.2.2"], - ["pyside", "1.2.2"], - ["pyside-tools", "0.2.15"], - ["pyside-examples", "master"], - ], - '1.2.1': [ - ["shiboken", "1.2.1"], - ["pyside", "1.2.1"], - ["pyside-tools", "0.2.15"], - ["pyside-examples", "master"], - ], - '1.2.0': [ - ["shiboken", "1.2.0"], - ["pyside", "1.2.0"], - ["pyside-tools", "0.2.14"], - ["pyside-examples", "master"], - ], - '1.1.2': [ - ["shiboken", "1.1.2"], - ["pyside", "1.1.2"], - ["pyside-tools", "0.2.14"], - ["pyside-examples", "master"], - ], - '1.1.1': [ - ["shiboken", "1.1.1"], - ["pyside", "1.1.1"], - ["pyside-tools", "0.2.14"], - ["pyside-examples", "master"], - ], -} + # Add the current timestamp to the version number, to suggest it is a development snapshot + # build. + if OPTION_SNAPSHOT_BUILD: + final_version += ".dev{}".format(get_package_timestamp()) + return final_version + +# The __version__ variable is just for PEP compliancy, and shoudn't be used as a value source. +__version__ = get_package_version() + +# Buildable extensions. +containedModules = ['shiboken2', 'pyside2', 'pyside2-tools'] + +# Git submodules: ["submodule_name", "location_relative_to_sources_folder"] +submodules = [["pyside2-tools"], + ["pyside2-examples"], + ["wiki", ".."]] pyside_package_dir_name = "pyside_package" @@ -190,14 +173,13 @@ except ImportError: from ez_setup import use_setuptools use_setuptools() -import os import sys import platform -import time 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 @@ -230,7 +212,6 @@ from utils import makefile from utils import copyfile from utils import copydir from utils import run_process_output, run_process -from utils import has_option from utils import option_value from utils import update_env_path from utils import init_msvc_env @@ -239,12 +220,13 @@ from utils import filter_match from utils import osx_fix_rpaths_for_library from utils import copy_icu_libs from utils import find_files_using_glob + from textwrap import dedent # guess a close folder name for extensions def get_extension_folder(ext): - candidates = containedModules - for gitModules in submodules[__version__]: + candidates = list(containedModules) + for gitModules in submodules: candidates.append(gitModules[0]) folder = difflib.get_close_matches(ext, candidates)[0] return folder @@ -279,8 +261,8 @@ OPTION_CMAKE = option_value("cmake") OPTION_OPENSSL = option_value("openssl") OPTION_ONLYPACKAGE = has_option("only-package") OPTION_STANDALONE = has_option("standalone") -OPTION_VERSION = option_value("version") -OPTION_LISTVERSIONS = has_option("list-versions") +OPTION_VERSION = option_value("version") # Deprecated +OPTION_LISTVERSIONS = has_option("list-versions") # Deprecated OPTION_MAKESPEC = option_value("make-spec") OPTION_IGNOREGIT = has_option("ignore-git") OPTION_NOEXAMPLES = has_option("no-examples") # don't include pyside2-examples @@ -303,6 +285,7 @@ OPTION_QT_CONF_PREFIX = option_value("qt-conf-prefix") OPTION_QT_SRC = option_value("qt-src-dir") OPTION_ICULIB = option_value("iculib-url") # Deprecated OPTION_VERBOSE_BUILD = has_option("verbose-build") +OPTION_SANITIZE_ADDRESS = has_option("sanitize-address") # This is used automatically by distutils.command.install object, to specify final installation # location. @@ -373,14 +356,6 @@ if OPTION_ICULIB: if not OPTION_STANDALONE: print("--iculib-url option is a no-op option and will be removed soon.") -# Show available versions -if OPTION_LISTVERSIONS: - for v in submodules: - print("%s" % (v)) - for m in submodules[v]: - print(" %s %s" % (m[0], m[1])) - sys.exit(1) - # Change the cwd to our source dir try: this_file = __file__ @@ -389,25 +364,16 @@ except NameError: this_file = os.path.abspath(this_file) if os.path.dirname(this_file): os.chdir(os.path.dirname(this_file)) -script_dir = os.getcwd() - -# Change package version -if OPTION_VERSION: - if OPTION_IGNOREGIT: - print("Option --version can not be used together with option --ignore-git") - sys.exit(1) - if not os.path.isdir(".git"): - print("Option --version is available only when pyside2-setup was cloned from git repository") - sys.exit(1) - if not OPTION_VERSION in submodules: - print("""Invalid version specified %s -Use --list-versions option to get list of available versions""" % OPTION_VERSION) - sys.exit(1) - __version__ = OPTION_VERSION def is_debug_python(): return getattr(sys, "gettotalrefcount", None) is not None +if OPTION_NOEXAMPLES: + # Remove pyside2-examples from submodules so they will not be included. + for idx, item in enumerate(submodules): + if item[0].startswith('pyside2-examples'): + del submodules[idx] + # Return a prefix suitable for the _install/_build directory def prefix(): virtualEnvName = os.environ.get('VIRTUAL_ENV', None) @@ -421,65 +387,56 @@ def prefix(): # Initialize, pull and checkout submodules def prepareSubModules(): - print("Initializing submodules for PySide2 version %s" % __version__) + print("Initializing submodules for PySide2 version: {}".format(get_package_version())) submodules_dir = os.path.join(script_dir, "sources") + # Create list of [name, desired branch, absolute path, desired branch] # and determine whether all submodules are present needInitSubModules = False - modulesList = [] - for m in submodules[__version__]: + + for m in submodules: module_name = m[0] - module_version = m[1] - module_dir = m[2] if len(m) > 2 else '' + module_dir = m[1] if len(m) > 1 else '' module_dir = os.path.join(submodules_dir, module_dir, module_name) # Check for non-empty directory (repository checked out) if not os.listdir(module_dir): needInitSubModules = True - modulesList.append([module_name, module_version, module_dir]) + break + if needInitSubModules: git_update_cmd = ["git", "submodule", "update", "--init"] if run_process(git_update_cmd) != 0: - raise DistutilsSetupError("Failed to initialize the git submodules") + m = "Failed to initialize the git submodules: update --init failed" + raise DistutilsSetupError(m) git_pull_cmd = ["git", "submodule", "foreach", "git", "fetch", "--all"] if run_process(git_pull_cmd) != 0: - raise DistutilsSetupError("Failed to initialize the git submodules") + m = "Failed to initialize the git submodules: git fetch --all failed" + raise DistutilsSetupError(m) else: - print("All submodules present...") - # Ensure all submodules have the correct branch checked out - for m in modulesList: - module_name = m[0] - module_version = m[1] - module_dir = m[2] - os.chdir(module_dir) - currentBranch = '' - branches = set() - for line in run_process_output(['git', 'branch']): - if line.startswith('* '): - currentBranch = line[2:len(line)] - else: - branches.add(line.strip()) - if currentBranch != module_version: - if not module_version in branches: - print("Creating tracking branch %s for submodule %s" % \ - (module_version, module_name)) - git_create_branch_cmd = ["git", "branch", "--track", module_version, - "origin/" + module_version] - if run_process(git_create_branch_cmd) != 0: - raise DistutilsSetupError("Failed to create a tracking branch %s for %s" % \ - (module_version, module_name)) - print("Checking out submodule %s to branch %s (from %s)" % (module_name, module_version, currentBranch)) - git_checkout_cmd = ["git", "checkout", module_version] - if run_process(git_checkout_cmd) != 0: - raise DistutilsSetupError("Failed to initialize the git submodule %s" % module_name) - else: - print("Submodule %s has branch %s checked out" % (module_name, module_version)) - os.chdir(script_dir) + print("All submodules present.") + + git_update_cmd = ["git", "submodule", "update"] + if run_process(git_update_cmd) != 0: + m = "Failed to checkout the correct git submodules SHA1s." + raise DistutilsSetupError(m) + +# Single global instance of QtInfo to be used later in multiple code paths. +qtinfo = QtInfo(QMAKE_COMMAND) + +def get_qt_version(): + qt_version = qtinfo.version + + if not qt_version: + log.error("Failed to query the Qt version with qmake %s" % self.qtinfo.qmake_command) + sys.exit(1) + + return qt_version def prepareBuild(): if os.path.isdir(".git") and not OPTION_IGNOREGIT and not OPTION_ONLYPACKAGE and not OPTION_REUSE_BUILD: prepareSubModules() # Clean up temp and package folders - for n in [pyside_package_dir_name, "build", "PySide2-%s" % __version__]: + for n in [pyside_package_dir_name, "build"]: d = os.path.join(script_dir, n) if os.path.isdir(d): print("Removing %s" % d) @@ -495,29 +452,14 @@ def prepareBuild(): os.makedirs(pkg_dir) # locate Qt sources for the documentation if OPTION_QT_SRC is None: - qmakeOutput = run_process_output([OPTION_QMAKE, '-query', 'QT_INSTALL_PREFIX']) - if qmakeOutput: + installPrefix = qtinfo.prefix_dir + if installPrefix: global qtSrcDir - installPrefix = qmakeOutput[0].rstrip() if installPrefix.endswith("qtbase"): # In-source, developer build qtSrcDir = installPrefix else: # SDK: Use 'Src' directory qtSrcDir = os.path.join(os.path.dirname(installPrefix), 'Src', 'qtbase') -def get_qt_version(computed_qtinfo = None): - if not computed_qtinfo: - qtinfo = QtInfo(QMAKE_COMMAND) - else: - qtinfo = computed_qtinfo - - qt_version = qtinfo.version - - if not qt_version: - log.error("Failed to query the Qt version with qmake %s" % self.qtinfo.qmake_command) - sys.exit(1) - - return qt_version - class pyside_install(_install): def __init__(self, *args, **kwargs): _install.__init__(self, *args, **kwargs) @@ -580,7 +522,8 @@ if wheel_module_exists: # Example: PySide2-5.6-5.6.4-cp27-cp27m-macosx_10_10_intel.whl # The PySide2 version is "5.6. The built against Qt version is "5.6.4. qt_version = get_qt_version() - wheel_version = "{}-{}".format(__version__, qt_version) + package_version = get_package_version() + wheel_version = "{}-{}".format(package_version, qt_version) components = (_safer_name(self.distribution.get_name()), wheel_version) if self.build_number: @@ -671,7 +614,7 @@ class pyside_build(_build): platform_arch = platform.architecture()[0] log.info("Python architecture is %s" % platform_arch) - build_type = OPTION_DEBUG and "Debug" or "Release" + build_type = "Debug" if OPTION_DEBUG else "Release" if OPTION_RELWITHDEBINFO: build_type = 'RelWithDebInfo' @@ -826,9 +769,9 @@ class pyside_build(_build): log.error("Failed to locate a dynamic Python library, using %s" % py_library) - self.qtinfo = QtInfo(QMAKE_COMMAND) + self.qtinfo = qtinfo qt_dir = os.path.dirname(OPTION_QMAKE) - qt_version = get_qt_version(self.qtinfo) + qt_version = get_qt_version() # Update the PATH environment variable additionalPaths = [py_scripts_dir, qt_dir] @@ -885,7 +828,7 @@ class pyside_build(_build): self.shiboken_build_dir = os.path.join(self.build_dir, "shiboken2") log.info("=" * 30) - log.info("Package version: %s" % __version__) + log.info("Package version: %s" % get_package_version()) log.info("Build type: %s" % self.build_type) log.info("Build tests: %s" % self.build_tests) log.info("-" * 3) @@ -929,6 +872,9 @@ class pyside_build(_build): log.info("-" * 3) if sys.platform == 'win32': log.info("OpenSSL dll directory: %s" % OPTION_OPENSSL) + if sys.platform == 'darwin': + pyside_macos_deployment_target = pyside_build.macos_pyside_min_deployment_target() + log.info("MACOSX_DEPLOYMENT_TARGET set to: {}".format(pyside_macos_deployment_target)) log.info("=" * 30) # Prepare folders @@ -944,7 +890,7 @@ class pyside_build(_build): if not OPTION_ONLYPACKAGE: # Build extensions - for ext in containedModules + ['pyside2-tools']: + for ext in containedModules: self.build_extension(ext) if OPTION_BUILDTESTS: @@ -973,20 +919,57 @@ class pyside_build(_build): log.info('*** Build completed') @staticmethod - def macos_min_deployment_target(): - # If no explicit minimum deployment target is provided to setup.py, then use the current - # build OS version. Otherwise use the provided version. - current_os_version, _, _ = platform.mac_ver() - current_os_version = '.'.join(current_os_version.split('.')[:2]) - deployment_target = current_os_version - if OPTION_OSX_DEPLOYMENT_TARGET: - deployment_target = OPTION_OSX_DEPLOYMENT_TARGET + def macos_qt_min_deployment_target(): + target = qtinfo.macos_min_deployment_target - return deployment_target + if not target: + raise DistutilsSetupError("Failed to query for Qt's QMAKE_MACOSX_DEPLOYMENT_TARGET.") + return target @staticmethod + @memoize + def macos_pyside_min_deployment_target(): + """ + Compute and validate PySide2 MACOSX_DEPLOYMENT_TARGET value. Candidate sources that are + considered: + - setup.py provided value + - maximum value between minimum deployment target of the Python interpreter and the + minimum deployment target of the Qt libraries. + If setup.py value is provided, that takes precedence. + Otherwise use the maximum of the above mentioned two values. + """ + python_target = get_config_var('MACOSX_DEPLOYMENT_TARGET') or None + qt_target = pyside_build.macos_qt_min_deployment_target() + setup_target = OPTION_OSX_DEPLOYMENT_TARGET + + qt_target_split = [int(x) for x in qt_target.split('.')] + if python_target: + python_target_split = [int(x) for x in python_target.split('.')] + if setup_target: + setup_target_split = [int(x) for x in setup_target.split('.')] + + message = "Can't set MACOSX_DEPLOYMENT_TARGET value to {} because " \ + "{} was built with minimum deployment target set to {}." + # setup.py provided OPTION_OSX_DEPLOYMENT_TARGET value takes precedence. + if setup_target: + if python_target and setup_target_split < python_target_split: + raise DistutilsSetupError(message.format(setup_target, "Python", python_target)) + if setup_target_split < qt_target_split: + raise DistutilsSetupError(message.format(setup_target, "Qt", qt_target)) + # All checks clear, use setup.py provided value. + return setup_target + + # Setup.py value not provided, use same value as provided by Qt. + if python_target: + maximum_target = '.'.join([str(e) for e in max(python_target_split, qt_target_split)]) + else: + maximum_target = qt_target + return maximum_target + + @staticmethod + @memoize def macos_plat_name(): - deployment_target = pyside_build.macos_min_deployment_target() + deployment_target = pyside_build.macos_pyside_min_deployment_target() # Example triple "macosx-10.12-x86_64". plat = get_platform().split("-") plat_name = "{}-{}-{}".format(plat[0], deployment_target, plat[2]) @@ -1071,6 +1054,13 @@ class pyside_build(_build): if OPTION_VERBOSE_BUILD: cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON") + if OPTION_SANITIZE_ADDRESS: + # Some simple sanity checking. Only use at your own risk. + if sys.platform.startswith('linux') or sys.platform.startswith('darwin'): + cmake_cmd.append("-DSANITIZE_ADDRESS=ON") + else: + raise DistutilsSetupError("Address sanitizer can only be used on Linux and macOS.") + if extension.lower() == "pyside2": pyside_qt_conf_prefix = '' if OPTION_QT_CONF_PREFIX: @@ -1082,6 +1072,17 @@ class pyside_build(_build): pyside_qt_conf_prefix = '"."' cmake_cmd.append("-DPYSIDE_QT_CONF_PREFIX=%s" % 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={0}".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={0}".format(timestamp)) + if extension.lower() == "shiboken2": cmake_cmd.append("-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=yes") if sys.version_info[0] > 2: @@ -1114,7 +1115,7 @@ class pyside_build(_build): # set its own minimum deployment target environment variable which is # based on the python interpreter sysconfig value. Doing so could break the # detected clang include paths for example. - deployment_target = pyside_build.macos_min_deployment_target() + deployment_target = pyside_build.macos_pyside_min_deployment_target() cmake_cmd.append("-DCMAKE_OSX_DEPLOYMENT_TARGET={0}".format(deployment_target)) os.environ['MACOSX_DEPLOYMENT_TARGET'] = deployment_target @@ -1156,8 +1157,6 @@ class pyside_build(_build): def prepare_packages(self): try: log.info("Preparing packages...") - version_str = "%sqt%s%s" % (__version__, self.qtinfo.version.replace(".", "")[0:3], - self.debug and "dbg" or "") vars = { "site_packages_dir": self.site_packages_dir, "sources_dir": self.sources_dir, @@ -1176,7 +1175,6 @@ class pyside_build(_build): "qt_prefix_dir": self.qtinfo.prefix_dir, "qt_translations_dir": self.qtinfo.translations_dir, "qt_qml_dir": self.qtinfo.qml_dir, - "version": version_str, } os.chdir(self.script_dir) @@ -1193,20 +1191,8 @@ class pyside_build(_build): # 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") - - try: - with open(config_path) as f: - scoped_locals = {} - code = compile(f.read(), config_path, 'exec') - exec(code, scoped_locals, scoped_locals) - config = {} - config['built_modules'] = scoped_locals['built_modules'] - config['shiboken_library_soversion'] = scoped_locals['shiboken_library_soversion'] - config['pyside_library_soversion'] = scoped_locals['pyside_library_soversion'] - return config - except IOError as e: - print("get_built_pyside_config: Couldn't find file: {}.".format(config_path)) - raise + config = get_python_dict(config_path) + return config def prepare_packages_posix(self, vars): executables = [] @@ -1374,7 +1360,7 @@ class pyside_build(_build): # <qt>/translations/* -> <setup>/PySide2/Qt/translations copydir("{qt_translations_dir}", "{pyside_package_dir}/PySide2/Qt/translations", - filter=["*.qm"], + filter=["*.qm", "*.pak"], force=False, vars=vars) @@ -1463,12 +1449,16 @@ class pyside_build(_build): # <qt>/translations/* -> <setup>/PySide2/Qt/translations copydir("{qt_translations_dir}", "{pyside_package_dir}/PySide2/Qt/translations", - filter=["*.qm"], + filter=["*.qm", "*.pak"], force=False, vars=vars) def prepare_packages_win32(self, vars): - pdbs = ['*.pdb'] if self.debug or self.build_type == 'RelWithDebInfo' else [] + # 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 copydir( "{site_packages_dir}/PySide2", @@ -1476,18 +1466,19 @@ class pyside_build(_build): vars=vars) built_modules = self.get_built_pyside_config(vars)['built_modules'] - if self.debug or self.build_type == 'RelWithDebInfo': - # <build>/pyside2/PySide2/*.pdb -> <setup>/PySide2 - copydir( - "{build_dir}/pyside2/PySide2", - "{pyside_package_dir}/PySide2", - filter=pdbs, - recursive=False, vars=vars) + # <build>/pyside2/PySide2/*.pdb -> <setup>/PySide2 + copydir( + "{build_dir}/pyside2/PySide2", + "{pyside_package_dir}/PySide2", + filter=pdbs, + 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) @@ -1500,12 +1491,14 @@ class pyside_build(_build): "{site_packages_dir}/{shiboken_module_name}", "{pyside_package_dir}/PySide2/{shiboken_module_name}", vars=vars) - if self.debug or self.build_type == 'RelWithDebInfo': - copydir( - "{build_dir}/shiboken2/shibokenmodule", - "{pyside_package_dir}/PySide2", - filter=pdbs, - recursive=False, 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/site-packages/pyside2uic/* -> <setup>/pyside2uic copydir( "{site_packages_dir}/pyside2uic", @@ -1515,6 +1508,7 @@ class pyside_build(_build): 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", @@ -1523,33 +1517,46 @@ class pyside_build(_build): "{install_dir}/bin/pyside2-uic", "{pyside_package_dir}/PySide2/scripts/uic.py", force=False, vars=vars) + # <install>/bin/*.exe,*.dll,*.pdb -> PySide2/ copydir( "{install_dir}/bin/", "{pyside_package_dir}/PySide2", - filter=["*.exe", "*.dll"] + pdbs, + 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) + # <install>/lib/*.lib -> PySide2/ copydir( "{install_dir}/lib/", "{pyside_package_dir}/PySide2", filter=["*.lib"], recursive=False, vars=vars) + # <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"), @@ -1562,6 +1569,7 @@ class pyside_build(_build): 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=[ @@ -1569,60 +1577,100 @@ class pyside_build(_build): "ssleay32.dll"], force=False, vars=vars) - # <qt>/bin/*.dll -> <setup>/PySide2 + # <qt>/bin/*.dll and Qt *.exe -> <setup>/PySide2 + qt_artifacts_permanent = [ + "opengl*.dll", + "d3d*.dll", + "designer.exe", + "linguist.exe", + "lrelease.exe", + "lupdate.exe", + "lconvert.exe", + "qtdiag.exe" + ] copydir("{qt_bin_dir}", "{pyside_package_dir}/PySide2", - filter=[ - "*.dll", - "designer.exe", - "linguist.exe", - "lrelease.exe", - "lupdate.exe", - "lconvert.exe"], - ignore=["*d4.dll"], + filter=qt_artifacts_permanent, recursive=False, vars=vars) - if self.debug: - # <qt>/bin/*d4.dll -> <setup>/PySide2 - copydir("{qt_bin_dir}", "{pyside_package_dir}/PySide2", - filter=["*d4.dll"] + pdbs, - recursive=False, vars=vars) - if self.debug or self.build_type == 'RelWithDebInfo': - # <qt>/lib/*.pdb -> <setup>/PySide2 - copydir("{qt_lib_dir}", "{pyside_package_dir}/PySide2", - filter=["*.pdb"], - recursive=False, vars=vars) + # <qt>/bin/*.dll and Qt *.pdbs -> <setup>/PySide2 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"] + 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] + + # If qt is not a debug_and_release build, that means there is only one set of shared + # libraries, so we can just copy them. + if qtinfo.build_type != 'debug_and_release': + if filter_match(file_name, release): + return True + return False + + # In debug_and_release case, choosing which files to copy is more difficult. We want + # to copy only the files that match the PySide2 build type. So if PySide2 is built in + # debug mode, we want to copy only Qt debug libraries (ending with "d.dll"). Or vice + # versa. The problem is that some libraries have "d" as the last character of the actual + # library name (for example Qt5Gamepad.dll and Qt5Gamepadd.dll). So we can't just + # match a pattern ending in "d". Instead we check if there exists a file with the same + # name plus an additional "d" at the end, and using that information we can judge if + # the currently processed file is a debug or release file. + + # e.g. ["Qt5Cored", ".dll"] + file_split = os.path.splitext(file_name) + file_base_name = file_split[0] + file_ext = file_split[1] + # e.g. "/home/work/qt/qtbase/bin" + file_path_dir_name = os.path.dirname(file_full_path) + # e.g. "Qt5Coredd" + maybe_debug_name = file_base_name + 'd' + if self.debug: + filter = debug + def predicate(path): return not os.path.exists(path) + else: + filter = release + def predicate(path): return os.path.exists(path) + # e.g. "/home/work/qt/qtbase/bin/Qt5Coredd.dll" + other_config_path = os.path.join(file_path_dir_name, maybe_debug_name + file_ext) + + if filter_match(file_name, filter) and predicate(other_config_path): + return True + return False - # I think these are the qt-mobility DLLs, at least some are, - # so let's copy them too - # <qt>/lib/*.dll -> <setup>/PySide2 - copydir("{qt_lib_dir}", "{pyside_package_dir}/PySide2", - filter=["*.dll"], - ignore=["*d?.dll"], + 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) - if self.debug: - # <qt>/lib/*d4.dll -> <setup>/PySide2 - copydir("{qt_lib_dir}", "{pyside_package_dir}/PySide2", - filter=["*d?.dll"], - recursive=False, vars=vars) - if self.debug or self.build_type == 'RelWithDebInfo': - # <qt>/lib/*pdb -> <setup>/PySide2 - copydir("{qt_lib_dir}", "{pyside_package_dir}/PySide2", - filter=pdbs, - recursive=False, vars=vars) # <qt>/plugins/* -> <setup>/PySide2/plugins + plugin_dll_patterns = ["*{}.dll"] + if copy_pdbs: + plugin_dll_patterns += ["*{}.pdb"] + plugin_dll_filter = functools.partial(qt_build_config_filter, plugin_dll_patterns) copydir("{qt_plugins_dir}", "{pyside_package_dir}/PySide2/plugins", - filter=["*.dll"] + pdbs, + file_filter_function=plugin_dll_filter, vars=vars) + # <qt>/translations/* -> <setup>/PySide2/translations copydir("{qt_translations_dir}", "{pyside_package_dir}/PySide2/translations", - filter=["*.qm"], + filter=["*.qm", "*.pak"], force=False, vars=vars) # <qt>/qml/* -> <setup>/PySide2/qml + qml_dll_patterns = ["*{}.dll"] + if copy_pdbs: + qml_dll_patterns += ["*{}.pdb"] + qml_ignore = [a.format('') for a in qml_dll_patterns] + qml_dll_filter = functools.partial(qt_build_config_filter, qml_dll_patterns) copydir("{qt_qml_dir}", "{pyside_package_dir}/PySide2/qml", - filter=None, + ignore=qml_ignore, + force=False, + recursive=True, + vars=vars) + copydir("{qt_qml_dir}", "{pyside_package_dir}/PySide2/qml", + file_filter_function=qml_dll_filter, force=False, recursive=True, vars=vars) @@ -1633,30 +1681,24 @@ class pyside_build(_build): recursive=False, vars=vars) + filter = 'QtWebEngineProcess{}.exe'.format('d' if self.debug else '') copydir("{qt_bin_dir}", "{pyside_package_dir}/PySide2", - filter=["QtWebEngineProcess*.exe"], + filter=[filter], recursive=False, vars=vars) self.prepare_standalone_clang(is_win=True) # pdb files for libshiboken and libpyside - if self.debug or self.build_type == 'RelWithDebInfo': - # XXX dbgPostfix gives problems - the structure in shiboken2/data should be re-written! - # Not sure what the above refers to, but because both the extension module - # (shiboken2.pyd) and the shared library (shiboken2.dll) have the same basename, - # the pdb file gets overwritten. This doesn't happen on Unix because the shared library - # has a 'lib' prefix in the basename. - # @TODO Change the shared library name on Windows. - 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) + 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) def prepare_standalone_clang(self, is_win = False): """ Copies the libclang library to the pyside package so that shiboken exceutable works. """ @@ -1786,7 +1828,7 @@ if wheel_module_exists: setup( name = "PySide2", - version = __version__, + version = get_package_version(), description = ("Python bindings for the Qt cross-platform application and UI framework"), long_description = README + "\n\n" + CHANGES, classifiers = [ diff --git a/sources/pyside2/CMakeLists.txt b/sources/pyside2/CMakeLists.txt index b16bcb55e..4db611f0d 100644 --- a/sources/pyside2/CMakeLists.txt +++ b/sources/pyside2/CMakeLists.txt @@ -21,6 +21,27 @@ else() find_package(PythonLibs 2.6) endif() +set(PYSIDE_VERSION_FILE_PATH "${CMAKE_SOURCE_DIR}/pyside_version.py") +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + ${PYSIDE_VERSION_FILE_PATH} +) +execute_process( + COMMAND ${PYTHON_EXECUTABLE} "${PYSIDE_VERSION_FILE_PATH}" + OUTPUT_VARIABLE PYSIDE_VERSION_OUTPUT + ERROR_VARIABLE PYSIDE_VERSION_OUTPUT_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE) +if (NOT PYSIDE_VERSION_OUTPUT) + message(FATAL_ERROR "Could not identify PySide2 version. Error: ${PYSIDE_VERSION_OUTPUT_ERROR}") +endif() + +list(GET PYSIDE_VERSION_OUTPUT 0 BINDING_API_MAJOR_VERSION) +list(GET PYSIDE_VERSION_OUTPUT 1 BINDING_API_MINOR_VERSION) +list(GET PYSIDE_VERSION_OUTPUT 2 BINDING_API_MICRO_VERSION) +# a - alpha, b - beta, rc - rc +list(GET PYSIDE_VERSION_OUTPUT 3 BINDING_API_PRE_RELEASE_VERSION_TYPE) +# the number of the pre release (alpha1, beta3, rc7, etc.) +list(GET PYSIDE_VERSION_OUTPUT 4 BINDING_API_PRE_RELEASE_VERSION) + macro(get_python_extension_suffix) # Result of imp.get_suffixes() depends on the platform, but generally looks something like: # [('.cpython-34m-x86_64-linux-gnu.so', 'rb', 3), ('.cpython-34m.so', 'rb', 3), @@ -161,19 +182,15 @@ if(NOT CMAKE_BUILD_TYPE) endif() set(BINDING_NAME PySide2) -set(BINDING_API_MAJOR_VERSION "2") -set(BINDING_API_MINOR_VERSION "0") -set(BINDING_API_MICRO_VERSION "0") -set(BINDING_API_RELEASE_LEVEL "alpha") # alpha, beta, rc, or final -set(BINDING_API_SERIAL 0) # leave as 0 when release level is final -set(BINDING_API_VERSION "${BINDING_API_MAJOR_VERSION}.${BINDING_API_MINOR_VERSION}.${BINDING_API_MICRO_VERSION}" CACHE STRING "PySide version" FORCE) + +set(BINDING_API_VERSION "${BINDING_API_MAJOR_VERSION}.${BINDING_API_MINOR_VERSION}.${BINDING_API_MICRO_VERSION}" CACHE STRING "PySide2 version" FORCE) set(PYSIDE_SO_VERSION ${BINDING_API_MAJOR_VERSION}.${BINDING_API_MINOR_VERSION}) -if (BINDING_API_RELEASE_LEVEL STREQUAL "final") +if (BINDING_API_PRE_RELEASE_VERSION_TYPE STREQUAL "") set(BINDING_API_VERSION_FULL "${BINDING_API_MAJOR_VERSION}.${BINDING_API_MINOR_VERSION}.${BINDING_API_MICRO_VERSION}" - CACHE STRING "PySide version [full]" FORCE) + CACHE STRING "PySide2 version [full]" FORCE) else() - set(BINDING_API_VERSION_FULL "${BINDING_API_MAJOR_VERSION}.${BINDING_API_MINOR_VERSION}.${BINDING_API_MICRO_VERSION}~${BINDING_API_RELEASE_LEVEL}${BINDING_API_SERIAL}" - CACHE STRING "PySide version [full]" FORCE) + set(BINDING_API_VERSION_FULL "${BINDING_API_MAJOR_VERSION}.${BINDING_API_MINOR_VERSION}.${BINDING_API_MICRO_VERSION}~${BINDING_API_PRE_RELEASE_VERSION_TYPE}${BINDING_API_PRE_RELEASE_VERSION}" + CACHE STRING "PySide2 version [full]" FORCE) endif() string(TIMESTAMP PYSIDE_BUILD_DATE "%Y-%m-%dT%H:%M:%S+00:00" UTC) @@ -181,6 +198,19 @@ 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 @@ -222,11 +252,6 @@ if(GIT_FOUND) endif() endif() -# Used by setup.py to know which symlink to resolve and copy in to the final package, in order to -# avoid resolving all symlinks and thus copying unnecessary duplicate files. -set(config_py_shiboken_library_version "") -set(config_py_pyside_library_version "") - include(PySideModules) macro(COLLECT_MODULE_IF_FOUND shortname) @@ -316,7 +341,7 @@ if(WIN32) list(APPEND ALL_OPTIONAL_MODULES AxContainer) endif() list(APPEND ALL_OPTIONAL_MODULES WebChannel WebEngineWidgets WebKit WebKitWidgets WebSockets) -if (Qt5Core_VERSION VERSION_GREATER 5.9.3 AND Qt5Core_VERSION VERSION_LESS 5.10.0) # Depending on fixes in Qt3D +if (Qt5Core_VERSION VERSION_GREATER 5.9.3) # Depending on fixes in Qt3D list(APPEND ALL_OPTIONAL_MODULES 3DCore 3DRender 3DInput 3DLogic 3DAnimation 3DExtras) endif() @@ -372,8 +397,6 @@ endif() # Define supported Qt Version set(SUPPORTED_QT_VERSION "${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}") -set(BINDING_VERSION ${BINDING_API_VERSION}.${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}) - # uninstall target configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake_uninstall.cmake" "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" @@ -382,16 +405,6 @@ add_custom_target(uninstall "${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake") -set(ARCHIVE_NAME pyside-qt${QT_VERSION_MAJOR}.${QT_VERSION_MINOR}+${BINDING_API_VERSION_FULL}) -add_custom_target(dist - COMMAND mkdir -p "${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}" && - git log > "${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}/ChangeLog" && - git archive --prefix=${ARCHIVE_NAME}/ HEAD --format=tar --output="${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar" && - tar -C "${CMAKE_BINARY_DIR}" --owner=root --group=root -r "${ARCHIVE_NAME}/ChangeLog" -f "${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar" && - bzip2 -f9 "${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar" && - echo "Source package created at ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.bz2.\n" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) - if (NOT PYTHON_SITE_PACKAGES) execute_process( COMMAND ${SHIBOKEN_PYTHON_INTERPRETER} -c "if True: @@ -424,6 +437,22 @@ else() message(STATUS "PySide will be generated using the protected hack!") endif() +# Build with Address sanitizer enabled if requested. This may break things, so use at your own risk. +if (SANITIZE_ADDRESS AND NOT MSVC) + # Currently this does not check that the clang / gcc version used supports Address sanitizer, + # so once again, use at your own risk. + add_compile_options("-fsanitize=address" "-g" "-fno-omit-frame-pointer") + # We need to add the sanitize address option to all linked executables / shared libraries + # so that proper sanitizer symbols are linked in. + # + # Note that when running tests, you may need to set an additional environment variable + # in set_tests_properties for shiboken2 / pyside tests, or exported in your shell. Address + # sanitizer will tell you what environment variable needs to be exported. For example: + # export DYLD_INSERT_LIBRARIES=/Applications/Xcode.app/Contents/Developer/Toolchains/ + # ./XcodeDefault.xctoolchain/usr/lib/clang/8.1.0/lib/darwin/libclang_rt.asan_osx_dynamic.dylib + set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_STANDARD_LIBRARIES} -fsanitize=address") +endif() + add_subdirectory(libpyside) find_package(Qt5Designer) if(Qt5UiTools_FOUND AND Qt5Designer_FOUND) diff --git a/sources/pyside2/PySide2/CMakeLists.txt b/sources/pyside2/PySide2/CMakeLists.txt index 21db337e9..971d0a9ef 100644 --- a/sources/pyside2/PySide2/CMakeLists.txt +++ b/sources/pyside2/PySide2/CMakeLists.txt @@ -10,6 +10,9 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/__init__.py.in" configure_file("${CMAKE_CURRENT_SOURCE_DIR}/_config.py.in" "${CMAKE_CURRENT_BINARY_DIR}/_config.py" @ONLY) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/../pyside_version.py" + "${CMAKE_CURRENT_BINARY_DIR}/_git_pyside_version.py" @ONLY) + 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" @@ -69,6 +72,8 @@ install(FILES "${CMAKE_CURRENT_BINARY_DIR}/__init__.py" DESTINATION "${PYTHON_SITE_PACKAGES}/${BINDING_NAME}${pyside2_SUFFIX}") install(FILES "${CMAKE_CURRENT_BINARY_DIR}/_config.py" DESTINATION "${PYTHON_SITE_PACKAGES}/${BINDING_NAME}${pyside2_SUFFIX}") +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/_git_pyside_version.py" + DESTINATION "${PYTHON_SITE_PACKAGES}/${BINDING_NAME}${pyside2_SUFFIX}") install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/typesystem_templates.xml DESTINATION share/PySide2${pyside_SUFFIX}/typesystems) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/pyside2_global.h diff --git a/sources/pyside2/PySide2/QtGui/typesystem_gui_common.xml b/sources/pyside2/PySide2/QtGui/typesystem_gui_common.xml index 12b0f3115..0f6d48968 100644 --- a/sources/pyside2/PySide2/QtGui/typesystem_gui_common.xml +++ b/sources/pyside2/PySide2/QtGui/typesystem_gui_common.xml @@ -1731,6 +1731,9 @@ <modify-argument index="return"> <replace-type modified-type="QString"/> </modify-argument> + <inject-code class="native" position="end"> + <insert-template name="return_QString_native"/> + </inject-code> <inject-code class="target" position="end"> <insert-template name="return_QString"/> </inject-code> diff --git a/sources/pyside2/PySide2/__init__.py.in b/sources/pyside2/PySide2/__init__.py.in index 92e52a81a..4ce266b69 100644 --- a/sources/pyside2/PySide2/__init__.py.in +++ b/sources/pyside2/PySide2/__init__.py.in @@ -1,14 +1,17 @@ __all__ = list("Qt" + body for body in "@all_module_shortnames@" .split(";")) -__version__ = "@BINDING_API_VERSION_FULL@" -__version_info__ = (@BINDING_API_MAJOR_VERSION@, @BINDING_API_MINOR_VERSION@, @BINDING_API_MICRO_VERSION@, "@BINDING_API_RELEASE_LEVEL@", @BINDING_API_SERIAL@) +__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 diff --git a/sources/pyside2/PySide2/_config.py.in b/sources/pyside2/PySide2/_config.py.in index db8a17210..6f8d022dc 100644 --- a/sources/pyside2/PySide2/_config.py.in +++ b/sources/pyside2/PySide2/_config.py.in @@ -4,3 +4,14 @@ built_modules = list(name for name in shiboken_library_soversion = str(@SHIBOKEN_SO_VERSION@) 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@ diff --git a/sources/pyside2/PySide2/typesystem_templates.xml b/sources/pyside2/PySide2/typesystem_templates.xml index 1715c0253..11a384263 100644 --- a/sources/pyside2/PySide2/typesystem_templates.xml +++ b/sources/pyside2/PySide2/typesystem_templates.xml @@ -286,6 +286,12 @@ <template name="return_QString"> %PYARG_0 = %CONVERTTOPYTHON[QString](%1); </template> + <template name="return_QString_native"> + if (%ISCONVERTIBLE[QString](%PYARG_0)) + %1 = %CONVERTTOCPP[QString](%PYARG_0); + else + qWarning("%TYPE::%FUNCTION_NAME: Argument is not convertible to unicode."); + </template> <template name="return_tuple_QValidator_QString_int"> %BEGIN_ALLOW_THREADS diff --git a/sources/pyside2/doc/inheritance_diagram.py b/sources/pyside2/doc/inheritance_diagram.py index a7f376ccd..038c22f29 100644 --- a/sources/pyside2/doc/inheritance_diagram.py +++ b/sources/pyside2/doc/inheritance_diagram.py @@ -52,15 +52,38 @@ from docutils.parsers.rst import directives from sphinx.ext.graphviz import render_dot_html, render_dot_latex from sphinx.util.compat import Directive - -class_sig_re = re.compile(r'''^([\w.]*\.)? # module names - (\w+) \s* $ # class/final module name - ''', re.VERBOSE) - - class InheritanceException(Exception): pass +# When passed something like: +# PySide2.QtCore.QStateMachine.SignalEvent +# try to import the underlying module and return a +# handle to the object. In a loop, import +# PySide2.QtCore.QStateMachine.SignalEvent +# PySide2.QtCore.QStateMachine +# PySide2.QtCore +# until the import succeeds and walk up the attributes +# to obtain the object + +def importClassOrModule(name): + components = name.split('.') + for i in range(len(components), 0, -1): + importPath = '.'.join(components[: i]) + try: + __import__(importPath) + except ImportError: + continue + if i == len(components): + return sys.modules[importPath] + remaining = components[i :] + cls = sys.modules[importPath] + for component in remaining: + try: + cls = getattr(cls, component) + except Exception: # No such attribute + return None + return cls + return None class InheritanceGraph(object): """ @@ -86,38 +109,13 @@ class InheritanceGraph(object): """ Import a class using its fully-qualified *name*. """ - try: - path, base = class_sig_re.match(name).groups() - except (AttributeError, ValueError): - raise InheritanceException('Invalid class or module %r specified ' - 'for inheritance diagram' % name) - - fullname = (path or '') + base - path = (path and path.rstrip('.') or '') - - # two possibilities: either it is a module, then import it - try: - __import__(fullname) - todoc = sys.modules[fullname] - except ImportError: - # else it is a class, then import the module - if not path: - if currmodule: - # try the current module - path = currmodule - else: - raise InheritanceException( - 'Could not import class %r specified for ' - 'inheritance diagram' % base) - try: - __import__(path) - todoc = getattr(sys.modules[path], base) - except (ImportError, AttributeError): - raise InheritanceException( - 'Could not import class or module %r specified for ' - 'inheritance diagram' % (path + '.' + base)) - - # If a class, just return it + todoc = importClassOrModule(name) + if not todoc and currmodule is not None: + todoc = importClassOrModule(currmodule + '.' + name) + if not todoc: + moduleStr = '(module {})'.format(currmodule) if currmodule else '' + raise InheritanceException('Could not import class {} specified for ' + 'inheritance diagram {}.'.format(name, moduleStr)) if inspect.isclass(todoc): return [todoc] elif inspect.ismodule(todoc): @@ -167,7 +165,7 @@ class InheritanceGraph(object): for cls in classes: recurse(cls) - return all_classes.values() + return list(all_classes.values()) def class_name(self, cls, parts=0): """Given a class object, return a fully-qualified name. @@ -200,8 +198,8 @@ class InheritanceGraph(object): 'shape': 'box', 'fontsize': 10, 'height': 0.25, - 'fontname': 'Vera Sans, DejaVu Sans, Liberation Sans, ' - 'Arial, Helvetica, sans', + 'fontname': '"Vera Sans, DejaVu Sans, Liberation Sans, ' + 'Arial, Helvetica, sans"', 'style': '"setlinewidth(0.5)"', } default_edge_attrs = { @@ -314,7 +312,8 @@ class InheritanceDiagram(Directive): def get_graph_hash(node): - return md5(node['content'] + str(node['parts'])).hexdigest()[-10:] + hashString = node['content'] + str(node['parts']) + return md5(hashString.encode('utf-8')).hexdigest()[-10:] def html_visit_inheritance_diagram(self, node): @@ -336,7 +335,7 @@ def html_visit_inheritance_diagram(self, node): urls[child['reftitle']] = '#' + child.get('refid') dotcode = graph.generate_dot(name, urls, env=self.builder.env) - render_dot_html(self, node, dotcode, [], 'inheritance', 'inheritance', + render_dot_html(self, node, dotcode, {}, 'inheritance', 'inheritance', alt='Inheritance diagram of ' + node['content']) raise nodes.SkipNode @@ -352,7 +351,7 @@ def latex_visit_inheritance_diagram(self, node): dotcode = graph.generate_dot(name, env=self.builder.env, graph_attrs={'size': '"6.0,6.0"'}) - render_dot_latex(self, node, dotcode, [], 'inheritance') + render_dot_latex(self, node, dotcode, {}, 'inheritance') raise nodes.SkipNode diff --git a/sources/pyside2/doc/pysideversion.rst b/sources/pyside2/doc/pysideversion.rst index 3bcebe0da..5ad308c1e 100644 --- a/sources/pyside2/doc/pysideversion.rst +++ b/sources/pyside2/doc/pysideversion.rst @@ -1,31 +1,31 @@ Getting PySide and Qt version ***************************** -PySide exports their version numbers among the version of Qt used to compile PySide in a pythonnic way, you can check it using the variables: +PySide exports their version numbers among the version of Qt used to compile PySide in a pythonic way, you can check it using the variables: :: - import PySide + import PySide2.QtCore # Prints PySide version # e.g. 1.0.2 - print PySide.__version__ + print(PySide2.__version__) # Gets a tuple with each version component # e.g. (1, 0, 2, 'final', 1) - print PySide.__version_info__ + print(PySide2.__version_info__) # Prints the Qt version used to compile PySide - # e.g. "4.7.2" - print PySide2.QtCore.__version__ + # e.g. "5.11.0" + print(PySide2.QtCore.__version__) # Gets a tuple with each version components of Qt used to compile PySide - # e.g. (4, 7, 2) - print PySide2.QtCore.__version_info__ + # e.g. (5, 11, 0) + print(PySide2.QtCore.__version_info__) Note that the Qt version used to compile PySide may differ from the version used to run PySide, to get the current running Qt version you can do: :: - print PySide2.QtCore.qVersion() + print(PySide2.QtCore.qVersion()) diff --git a/sources/pyside2/libpyside/pysidesignal.cpp b/sources/pyside2/libpyside/pysidesignal.cpp index 04b1cf1f4..a901d10af 100644 --- a/sources/pyside2/libpyside/pysidesignal.cpp +++ b/sources/pyside2/libpyside/pysidesignal.cpp @@ -413,11 +413,64 @@ PyObject* signalInstanceConnect(PyObject* self, PyObject* args, PyObject* kwds) sourceWalk = reinterpret_cast<PySideSignalInstance*>(sourceWalk->d->next); } } else { - //try the first signature + // Check signature of the slot (method or function) to match signal + int slotArgs = -1; + bool useSelf = false; + bool isMethod = PyMethod_Check(slot); + bool isFunction = PyFunction_Check(slot); + bool matchedSlot = false; + + QByteArray functionName; + PySideSignalInstance *it = source; + + if (isMethod || isFunction) { + PyObject *function = isMethod ? PyMethod_GET_FUNCTION(slot) : slot; + PyCodeObject *objCode = reinterpret_cast<PyCodeObject *>(PyFunction_GET_CODE(function)); + PyFunctionObject *function_obj = reinterpret_cast<PyFunctionObject *>(function); + functionName = Shiboken::String::toCString(function_obj->func_name); + useSelf = isMethod; + slotArgs = objCode->co_flags & CO_VARARGS ? -1 : objCode->co_argcount; + if (useSelf) + slotArgs -= 1; + + // Get signature args + bool isShortCircuit = false; + int signatureArgs = 0; + QStringList argsSignature; + + argsSignature = PySide::Signal::getArgsFromSignature(it->d->signature, + &isShortCircuit); + signatureArgs = argsSignature.length(); + + // Iterate the possible types of connection for this signal and compare + // it with slot arguments + if (signatureArgs != slotArgs) { + while (it->d->next != nullptr) { + it = it->d->next; + argsSignature = PySide::Signal::getArgsFromSignature(it->d->signature, + &isShortCircuit); + signatureArgs = argsSignature.length(); + if (signatureArgs == slotArgs) { + matchedSlot = true; + break; + } + } + } + } + + // Adding references to pyArgs PyList_Append(pyArgs, source->d->source); - Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(source->d->signature)); - PyList_Append(pyArgs, signature); + if (matchedSlot) { + // If a slot matching the same number of arguments was found, + // include signature to the pyArgs + Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(it->d->signature)); + PyList_Append(pyArgs, signature); + } else { + // Try the first by default if the slot was not found + Shiboken::AutoDecRef signature(PySide::Signal::buildQtCompatible(source->d->signature)); + PyList_Append(pyArgs, signature); + } PyList_Append(pyArgs, slot); match = true; } diff --git a/sources/pyside2/pyside_version.py b/sources/pyside2/pyside_version.py new file mode 100644 index 000000000..3a678cd14 --- /dev/null +++ b/sources/pyside2/pyside_version.py @@ -0,0 +1,49 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of PySide2. +## +## $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$ +## +############################################################################# + +major_version = "5" +minor_version = "11" +patch_version = "0" +pre_release_version_type = "a" # e.g. "a", "b", "rc". +pre_release_version = "1" # e.g "1", "2", (which means "beta1", "beta2", if type is "b") + +if __name__ == '__main__': + # Used by CMake. + print('{0};{1};{2};{3};{4}'.format(major_version, minor_version, patch_version, + pre_release_version_type, pre_release_version)) diff --git a/sources/pyside2/tests/QtWidgets/qvalidator_test.py b/sources/pyside2/tests/QtWidgets/qvalidator_test.py index 951d6b2b0..dd5eaadb3 100644 --- a/sources/pyside2/tests/QtWidgets/qvalidator_test.py +++ b/sources/pyside2/tests/QtWidgets/qvalidator_test.py @@ -29,38 +29,49 @@ from PySide2.QtCore import * from PySide2.QtGui import * from PySide2.QtWidgets import * +from PySide2.QtTest import * import unittest from helper import UsesQApplication class MyValidator1(QValidator): - def fixUp(self, input): + def fixup(self, input): return "fixed" def validate(self, input, pos): return (QValidator.Acceptable, "fixed", 1) class MyValidator2(QValidator): - def fixUp(self, input): + def fixup(self, input): return "fixed" def validate(self, input, pos): return (QValidator.Acceptable, "fixed") class MyValidator3(QValidator): - def fixUp(self, input): + def fixup(self, input): return "fixed" def validate(self, input, pos): return (QValidator.Acceptable,) class MyValidator4(QValidator): - def fixUp(self, input): + def fixup(self, input): return "fixed" def validate(self, input, pos): return QValidator.Acceptable +class MyValidator5(QValidator): + def validate(self, input, pos): + if input.islower(): + return (QValidator.Intermediate, input, pos) + else: + return (QValidator.Acceptable, input, pos) + + def fixup(self, input): + return "22" + class QValidatorTest(UsesQApplication): def testValidator1(self): line = QLineEdit() @@ -110,5 +121,13 @@ class QValidatorTest(UsesQApplication): self.assertEqual(line.text(), "foo") self.assertEqual(line.cursorPosition(), 3) + def testValidator5(self): + line = QLineEdit() + line.show() + line.setValidator(MyValidator5()) + line.setText("foo") + QTest.keyClick(line, Qt.Key_Return) + self.assertEqual(line.text(), "22") + if __name__ == '__main__': unittest.main() diff --git a/sources/pyside2/tests/pysidetest/version_test.py b/sources/pyside2/tests/pysidetest/version_test.py index 5901a56c1..01e88dbc6 100644 --- a/sources/pyside2/tests/pysidetest/version_test.py +++ b/sources/pyside2/tests/pysidetest/version_test.py @@ -33,8 +33,10 @@ from PySide2 import __version_info__, __version__, QtCore class CheckForVariablesTest(unittest.TestCase): def testVesions(self): - self.assertTrue(__version_info__ >= (1, 0, 0)) - self.assertTrue(__version_info__ < (99, 99, 99)) + version_tuple = (__version_info__[0], __version_info__[1], __version_info__[2]) + self.assertTrue(version_tuple >= (1, 0, 0)) + + self.assertTrue(version_tuple < (99, 99, 99)) self.assertTrue(__version__) self.assertTrue(QtCore.__version_info__ >= (4, 5, 0)) diff --git a/sources/pyside2/tests/signals/signal_signature_test.py b/sources/pyside2/tests/signals/signal_signature_test.py index 349619aac..e94c1722d 100644 --- a/sources/pyside2/tests/signals/signal_signature_test.py +++ b/sources/pyside2/tests/signals/signal_signature_test.py @@ -34,7 +34,11 @@ import unittest from PySide2.QtCore import * from helper import UsesQCoreApplication +called = False +name = "Old" class Obj(QObject): + dummySignalArgs = Signal(str) + numberSignal = Signal(int) def __init__(self): QObject.__init__(self) self.signal = '' @@ -42,8 +46,20 @@ class Obj(QObject): def connectNotify(self, signal): self.signal = signal + @staticmethod + def static_method(): + global called + called = True + + @staticmethod + def static_method_args(arg="default"): + global name + name = arg + def callback(arg=None): pass +def callback_empty(): + pass class TestConnectNotifyWithNewStyleSignals(UsesQCoreApplication): '''Test case for signal signature received by QObject::connectNotify().''' @@ -65,12 +81,34 @@ class TestConnectNotifyWithNewStyleSignals(UsesQCoreApplication): def testNewStyle(self): sender = Obj() - sender.destroyed.connect(callback) + sender.destroyed.connect(callback_empty) self.assertEqual(sender.signal.methodSignature(), 'destroyed()') sender.destroyed[QObject].connect(callback) self.assertEqual(sender.signal.methodSignature(), 'destroyed(QObject*)') + def testStaticSlot(self): + global called + sender = Obj() + sender.connect(sender, SIGNAL("dummySignal()"), Obj.static_method) + sender.emit(SIGNAL("dummySignal()")) + self.assertTrue(called) + + + def testStaticSlotArgs(self): + global name + sender = Obj() + sender.dummySignalArgs.connect(Obj.static_method_args) + sender.dummySignalArgs[str].emit("New") + self.assertEqual(name, "New") + + def testLambdaSlot(self): + sender = Obj() + sender.numberSignal[int].connect(lambda x: 42) + with self.assertRaises(IndexError): + sender.numberSignal[str].emit("test") + + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken2/ApiExtractor/doc/typesystem_specifying_types.rst b/sources/shiboken2/ApiExtractor/doc/typesystem_specifying_types.rst index 0d24a6d52..646e76043 100644 --- a/sources/shiboken2/ApiExtractor/doc/typesystem_specifying_types.rst +++ b/sources/shiboken2/ApiExtractor/doc/typesystem_specifying_types.rst @@ -150,6 +150,7 @@ enum-type <typesystem> <enum-type name="..." identified-by-value="..." + class="yes | no" since="..." flags="yes | no" flags-revision="..." @@ -179,6 +180,10 @@ enum-type Notice that the **enum-type** tag can either have **name** or **identified-by-value** but not both. + The *optional* boolean attribute **class** specifies whether the underlying + enumeration is a C++ 11 enumeration class. In that case, the enumeration values + need to be qualified by the enumeration name to match the C++ Syntax. + The **revision** attribute can be used to specify a revision for each type, easing the production of ABI compatible bindings. diff --git a/sources/shiboken2/ApiExtractor/typesystem.cpp b/sources/shiboken2/ApiExtractor/typesystem.cpp index 4a8d3063d..69dccbb86 100644 --- a/sources/shiboken2/ApiExtractor/typesystem.cpp +++ b/sources/shiboken2/ApiExtractor/typesystem.cpp @@ -58,6 +58,12 @@ static inline QString enumNameAttribute() { return QStringLiteral("enum-name"); static inline QString argumentTypeAttribute() { return QStringLiteral("argument-type"); } static inline QString returnTypeAttribute() { return QStringLiteral("return-type"); } static inline QString xPathAttribute() { return QStringLiteral("xpath"); } +static inline QString enumIdentifiedByValueAttribute() { return QStringLiteral("identified-by-value"); } + +static inline QString noAttributeValue() { return QStringLiteral("no"); } +static inline QString yesAttributeValue() { return QStringLiteral("yes"); } +static inline QString trueAttributeValue() { return QStringLiteral("true"); } +static inline QString falseAttributeValue() { return QStringLiteral("false"); } static QVector<CustomConversion *> customConversionsForReview; @@ -534,21 +540,22 @@ bool Handler::importFileElement(const QXmlStreamAttributes &atts) return true; } -bool Handler::convertBoolean(const QString &_value, const QString &attributeName, bool defaultValue) +static bool convertBoolean(const QString &value, const QString &attributeName, bool defaultValue) { - QString value = _value.toLower(); - if (value == QLatin1String("true") || value == QLatin1String("yes")) + if (value.compare(trueAttributeValue(), Qt::CaseInsensitive) == 0 + || value.compare(yesAttributeValue(), Qt::CaseInsensitive) == 0) { return true; - else if (value == QLatin1String("false") || value == QLatin1String("no")) + } + if (value.compare(falseAttributeValue(), Qt::CaseInsensitive) == 0 + || value.compare(noAttributeValue(), Qt::CaseInsensitive) == 0) { return false; - else { - QString warn = QStringLiteral("Boolean value '%1' not supported in attribute '%2'. Use 'yes' or 'no'. Defaulting to '%3'.") + } + const QString warn = QStringLiteral("Boolean value '%1' not supported in attribute '%2'. Use 'yes' or 'no'. Defaulting to '%3'.") .arg(value, attributeName, - defaultValue ? QLatin1String("yes") : QLatin1String("no")); + defaultValue ? yesAttributeValue() : noAttributeValue()); - qCWarning(lcShiboken).noquote().nospace() << warn; - return defaultValue; - } + qCWarning(lcShiboken).noquote().nospace() << warn; + return defaultValue; } static bool convertRemovalAttribute(const QString& removalAttribute, Modification& mod, QString& errorMsg) @@ -733,8 +740,8 @@ bool Handler::startElement(const QStringRef &n, const QXmlStreamAttributes &atts case StackElement::PrimitiveTypeEntry: attributes.insert(QLatin1String("target-lang-name"), QString()); attributes.insert(QLatin1String("target-lang-api-name"), QString()); - attributes.insert(QLatin1String("preferred-conversion"), QLatin1String("yes")); - attributes.insert(QLatin1String("preferred-target-lang-type"), QLatin1String("yes")); + attributes.insert(QLatin1String("preferred-conversion"), yesAttributeValue()); + attributes.insert(QLatin1String("preferred-target-lang-type"), yesAttributeValue()); attributes.insert(QLatin1String("default-constructor"), QString()); break; case StackElement::ContainerTypeEntry: @@ -750,23 +757,24 @@ bool Handler::startElement(const QStringRef &n, const QXmlStreamAttributes &atts attributes.insert(QLatin1String("flags-revision"), QString()); attributes.insert(QLatin1String("upper-bound"), QString()); attributes.insert(QLatin1String("lower-bound"), QString()); - attributes.insert(QLatin1String("force-integer"), QLatin1String("no")); - attributes.insert(QLatin1String("extensible"), QLatin1String("no")); - attributes.insert(QLatin1String("identified-by-value"), QString()); + attributes.insert(QLatin1String("force-integer"), noAttributeValue()); + attributes.insert(QLatin1String("extensible"), noAttributeValue()); + attributes.insert(enumIdentifiedByValueAttribute(), QString()); + attributes.insert(classAttribute(), falseAttributeValue()); break; case StackElement::ValueTypeEntry: attributes.insert(QLatin1String("default-constructor"), QString()); Q_FALLTHROUGH(); case StackElement::ObjectTypeEntry: - attributes.insert(QLatin1String("force-abstract"), QLatin1String("no")); - attributes.insert(QLatin1String("deprecated"), QLatin1String("no")); + attributes.insert(QLatin1String("force-abstract"), noAttributeValue()); + attributes.insert(QLatin1String("deprecated"), noAttributeValue()); attributes.insert(QLatin1String("hash-function"), QString()); - attributes.insert(QLatin1String("stream"), QLatin1String("no")); + attributes.insert(QLatin1String("stream"), noAttributeValue()); Q_FALLTHROUGH(); case StackElement::InterfaceTypeEntry: attributes[QLatin1String("default-superclass")] = m_defaultSuperclass; attributes.insert(QLatin1String("polymorphic-id-expression"), QString()); - attributes.insert(QLatin1String("delete-in-main-thread"), QLatin1String("no")); + attributes.insert(QLatin1String("delete-in-main-thread"), noAttributeValue()); attributes.insert(QLatin1String("held-type"), QString()); attributes.insert(QLatin1String("copyable"), QString()); Q_FALLTHROUGH(); @@ -775,10 +783,10 @@ bool Handler::startElement(const QStringRef &n, const QXmlStreamAttributes &atts attributes[QLatin1String("package")] = m_defaultPackage; attributes.insert(QLatin1String("expense-cost"), QLatin1String("1")); attributes.insert(QLatin1String("expense-limit"), QLatin1String("none")); - attributes.insert(QLatin1String("polymorphic-base"), QLatin1String("no")); - attributes.insert(QLatin1String("generate"), QLatin1String("yes")); + attributes.insert(QLatin1String("polymorphic-base"), noAttributeValue()); + attributes.insert(QLatin1String("generate"), yesAttributeValue()); attributes.insert(QLatin1String("target-type"), QString()); - attributes.insert(QLatin1String("generic-class"), QLatin1String("no")); + attributes.insert(QLatin1String("generic-class"), noAttributeValue()); break; case StackElement::FunctionTypeEntry: attributes.insert(QLatin1String("signature"), QString()); @@ -846,9 +854,10 @@ bool Handler::startElement(const QStringRef &n, const QXmlStreamAttributes &atts } if (element->type == StackElement::EnumTypeEntry) { + const QString identifiedByValue = attributes.value(enumIdentifiedByValueAttribute()); if (name.isEmpty()) { - name = attributes[QLatin1String("identified-by-value")]; - } else if (!attributes[QLatin1String("identified-by-value")].isEmpty()) { + name = identifiedByValue; + } else if (!identifiedByValue.isEmpty()) { m_error = QLatin1String("can't specify both 'name' and 'identified-by-value' attributes"); return false; } @@ -933,7 +942,11 @@ bool Handler::startElement(const QStringRef &n, const QXmlStreamAttributes &atts m_currentEnum = new EnumTypeEntry(QStringList(names.mid(0, names.size() - 1)).join(colonColon()), names.constLast(), since); - m_currentEnum->setAnonymous(!attributes[QLatin1String("identified-by-value")].isEmpty()); + if (!attributes.value(enumIdentifiedByValueAttribute()).isEmpty()) { + m_currentEnum->setEnumKind(EnumTypeEntry::AnonymousEnum); + } else if (convertBoolean(attributes.value(classAttribute()), classAttribute(), false)) { + m_currentEnum->setEnumKind(EnumTypeEntry::EnumClass); + } element->entry = m_currentEnum; m_currentEnum->setCodeGeneration(m_generate); m_currentEnum->setTargetLangPackage(m_defaultPackage); @@ -988,7 +1001,7 @@ bool Handler::startElement(const QStringRef &n, const QXmlStreamAttributes &atts if (!element->entry) element->entry = new ObjectTypeEntry(name, since); - element->entry->setStream(attributes[QLatin1String("stream")] == QLatin1String("yes")); + element->entry->setStream(attributes[QLatin1String("stream")] == yesAttributeValue()); ComplexTypeEntry *ctype = static_cast<ComplexTypeEntry *>(element->entry); ctype->setTargetLangPackage(attributes[QLatin1String("package")]); @@ -1174,7 +1187,7 @@ bool Handler::startElement(const QStringRef &n, const QXmlStreamAttributes &atts break; case StackElement::LoadTypesystem: attributes.insert(nameAttribute(), QString()); - attributes.insert(QLatin1String("generate"), QLatin1String("yes")); + attributes.insert(QLatin1String("generate"), yesAttributeValue()); break; case StackElement::NoNullPointers: attributes.insert(QLatin1String("default-value"), QString()); @@ -1193,28 +1206,28 @@ bool Handler::startElement(const QStringRef &n, const QXmlStreamAttributes &atts attributes.insert(QLatin1String("signature"), QString()); attributes.insert(QLatin1String("return-type"), QLatin1String("void")); attributes.insert(QLatin1String("access"), QLatin1String("public")); - attributes.insert(QLatin1String("static"), QLatin1String("no")); + attributes.insert(QLatin1String("static"), noAttributeValue()); break; case StackElement::ModifyFunction: attributes.insert(QLatin1String("signature"), QString()); attributes.insert(QLatin1String("access"), QString()); attributes.insert(QLatin1String("remove"), QString()); attributes.insert(QLatin1String("rename"), QString()); - attributes.insert(QLatin1String("deprecated"), QLatin1String("no")); + attributes.insert(QLatin1String("deprecated"), noAttributeValue()); attributes.insert(QLatin1String("associated-to"), QString()); - attributes.insert(QLatin1String("virtual-slot"), QLatin1String("no")); - attributes.insert(QLatin1String("thread"), QLatin1String("no")); - attributes.insert(QLatin1String("allow-thread"), QLatin1String("no")); + attributes.insert(QLatin1String("virtual-slot"), noAttributeValue()); + attributes.insert(QLatin1String("thread"), noAttributeValue()); + attributes.insert(QLatin1String("allow-thread"), noAttributeValue()); break; case StackElement::ModifyArgument: attributes.insert(QLatin1String("index"), QString()); attributes.insert(QLatin1String("replace-value"), QString()); - attributes.insert(QLatin1String("invalidate-after-use"), QLatin1String("no")); + attributes.insert(QLatin1String("invalidate-after-use"), noAttributeValue()); break; case StackElement::ModifyField: attributes.insert(nameAttribute(), QString()); - attributes.insert(QLatin1String("write"), QLatin1String("true")); - attributes.insert(QLatin1String("read"), QLatin1String("true")); + attributes.insert(QLatin1String("write"), trueAttributeValue()); + attributes.insert(QLatin1String("read"), trueAttributeValue()); attributes.insert(QLatin1String("remove"), QString()); break; case StackElement::Access: @@ -1245,7 +1258,7 @@ bool Handler::startElement(const QStringRef &n, const QXmlStreamAttributes &atts attributes.insert(QLatin1String("file"), QString()); break; case StackElement::TargetToNative: - attributes.insert(QLatin1String("replace"), QLatin1String("yes")); + attributes.insert(QLatin1String("replace"), yesAttributeValue()); break; case StackElement::AddConversion: attributes.insert(QLatin1String("type"), QString()); @@ -1429,7 +1442,7 @@ bool Handler::startElement(const QStringRef &n, const QXmlStreamAttributes &atts m_error = QLatin1String("Target to Native conversions can only be specified for custom conversion rules."); return false; } - bool replace = attributes[QLatin1String("replace")] == QLatin1String("yes"); + bool replace = attributes[QLatin1String("replace")] == yesAttributeValue(); static_cast<TypeEntry*>(m_current->entry)->customConversion()->setReplaceOriginalTargetToNativeConversions(replace); } break; @@ -1679,8 +1692,8 @@ bool Handler::startElement(const QStringRef &n, const QXmlStreamAttributes &atts QString read = attributes[QLatin1String("read")]; QString write = attributes[QLatin1String("write")]; - if (read == QLatin1String("true")) fm.modifiers |= FieldModification::Readable; - if (write == QLatin1String("true")) fm.modifiers |= FieldModification::Writable; + if (read == trueAttributeValue()) fm.modifiers |= FieldModification::Readable; + if (write == trueAttributeValue()) fm.modifiers |= FieldModification::Writable; m_contextStack.top()->fieldMods << fm; } @@ -1706,7 +1719,7 @@ bool Handler::startElement(const QStringRef &n, const QXmlStreamAttributes &atts } AddedFunction func(signature, attributes[QLatin1String("return-type")], since); - func.setStatic(attributes[QLatin1String("static")] == QLatin1String("yes")); + func.setStatic(attributes[QLatin1String("static")] == yesAttributeValue()); if (!signature.contains(QLatin1Char('('))) signature += QLatin1String("()"); m_currentSignature = signature; @@ -2273,19 +2286,6 @@ QString FlagsTypeEntry::targetLangPackage() const return m_enum->targetLangPackage(); } -void EnumTypeEntry::addEnumValueRedirection(const QString &rejected, const QString &usedValue) -{ - m_enumRedirections << EnumValueRedirection(rejected, usedValue); -} - -QString EnumTypeEntry::enumValueRedirection(const QString &value) const -{ - for (int i = 0; i < m_enumRedirections.size(); ++i) - if (m_enumRedirections.at(i).rejected == value) - return m_enumRedirections.at(i).used; - return QString(); -} - QString FlagsTypeEntry::qualifiedTargetLangName() const { return targetLangPackage() + QLatin1Char('.') + m_enum->targetLangQualifier() diff --git a/sources/shiboken2/ApiExtractor/typesystem.h b/sources/shiboken2/ApiExtractor/typesystem.h index c8a1c88fa..62d348f69 100644 --- a/sources/shiboken2/ApiExtractor/typesystem.h +++ b/sources/shiboken2/ApiExtractor/typesystem.h @@ -1042,21 +1042,15 @@ private: PrimitiveTypeEntry* m_referencedTypeEntry = nullptr; }; -struct EnumValueRedirection -{ - EnumValueRedirection() {} - EnumValueRedirection(const QString &rej, const QString &us) - : rejected(rej), - used(us) - { - } - QString rejected; - QString used; -}; - class EnumTypeEntry : public TypeEntry { public: + enum EnumKind { + CEnum, // Standard C: enum Foo { value1, value2 } + AnonymousEnum, // enum { value1, value2 } + EnumClass // C++ 11 : enum class Foo { value1, value2 } + }; + explicit EnumTypeEntry(const QString &nspace, const QString &enumName, double vr); QString targetLangPackage() const override; @@ -1077,6 +1071,9 @@ public: m_qualifier = q; } + EnumKind enumKind() const { return m_enumKind; } + void setEnumKind(EnumKind kind) { m_enumKind = kind; } + bool preferredConversion() const override; bool isBoundsChecked() const @@ -1120,7 +1117,7 @@ public: m_extensible = is; } - bool isEnumValueRejected(const QString &name) + bool isEnumValueRejected(const QString &name) const { return m_rejectedEnums.contains(name); } @@ -1133,9 +1130,6 @@ public: return m_rejectedEnums; } - void addEnumValueRedirection(const QString &rejected, const QString &usedValue); - QString enumValueRedirection(const QString &value) const; - bool forceInteger() const { return m_forceInteger; @@ -1145,14 +1139,7 @@ public: m_forceInteger = force; } - bool isAnonymous() const - { - return m_anonymous; - } - void setAnonymous(bool anonymous) - { - m_anonymous = anonymous; - } + bool isAnonymous() const { return m_enumKind == AnonymousEnum; } private: QString m_packageName; @@ -1163,15 +1150,17 @@ private: QString m_upperBound; QStringList m_rejectedEnums; - QVector<EnumValueRedirection> m_enumRedirections; FlagsTypeEntry *m_flags = nullptr; + EnumKind m_enumKind = CEnum; + bool m_extensible = false; bool m_forceInteger = false; - bool m_anonymous = false; }; +// EnumValueTypeEntry is used for resolving integer type templates +// like array<EnumValue>. class EnumValueTypeEntry : public TypeEntry { public: diff --git a/sources/shiboken2/ApiExtractor/typesystem_p.h b/sources/shiboken2/ApiExtractor/typesystem_p.h index fd67ef49b..d3485726e 100644 --- a/sources/shiboken2/ApiExtractor/typesystem_p.h +++ b/sources/shiboken2/ApiExtractor/typesystem_p.h @@ -153,7 +153,6 @@ private: QHash<QString, QString> *acceptedAttributes); bool importFileElement(const QXmlStreamAttributes &atts); - bool convertBoolean(const QString &, const QString &, bool); void addFlags(const QString &name, QString flagName, const QHash<QString, QString> &attributes, double since); diff --git a/sources/shiboken2/CMakeLists.txt b/sources/shiboken2/CMakeLists.txt index 006924ad9..ccabc72e3 100644 --- a/sources/shiboken2/CMakeLists.txt +++ b/sources/shiboken2/CMakeLists.txt @@ -11,12 +11,6 @@ find_package(Qt5 REQUIRED COMPONENTS Core Xml XmlPatterns) add_definitions(${Qt5Core_DEFINITIONS}) -set(shiboken_MAJOR_VERSION "2") -set(shiboken_MINOR_VERSION "0") -set(shiboken_MICRO_VERSION "0") -set(shiboken2_VERSION "${shiboken_MAJOR_VERSION}.${shiboken_MINOR_VERSION}.${shiboken_MICRO_VERSION}") -set(shiboken2_library_so_version "${shiboken_MAJOR_VERSION}.${shiboken_MINOR_VERSION}") - option(BUILD_TESTS "Build tests." TRUE) option(USE_PYTHON_VERSION "Use specific python version to build shiboken2." "") @@ -126,6 +120,31 @@ message(STATUS "CLANG builtins includes directory chosen: ${CLANG_BUILTIN_INCLUD set(CLANG_EXTRA_INCLUDES ${CLANG_DIR}/include) set(CLANG_EXTRA_LIBRARIES ${CLANG_LIBRARY}) +set(SHIBOKEN_VERSION_FILE_PATH "${CMAKE_SOURCE_DIR}/shiboken_version.py") +set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS + ${SHIBOKEN_VERSION_FILE_PATH} +) +execute_process( + COMMAND ${PYTHON_EXECUTABLE} "${SHIBOKEN_VERSION_FILE_PATH}" + OUTPUT_VARIABLE SHIBOKEN_VERSION_OUTPUT + ERROR_VARIABLE SHIBOKEN_VERSION_OUTPUT_ERROR + OUTPUT_STRIP_TRAILING_WHITESPACE) +if (NOT SHIBOKEN_VERSION_OUTPUT) + message(FATAL_ERROR "Could not identify shiboken version. \ + Error: ${SHIBOKEN_VERSION_OUTPUT_ERROR}") +endif() + +list(GET SHIBOKEN_VERSION_OUTPUT 0 shiboken_MAJOR_VERSION) +list(GET SHIBOKEN_VERSION_OUTPUT 1 shiboken_MINOR_VERSION) +list(GET SHIBOKEN_VERSION_OUTPUT 2 shiboken_MICRO_VERSION) +# a - alpha, b - beta, rc - rc +list(GET SHIBOKEN_VERSION_OUTPUT 3 shiboken_PRE_RELEASE_VERSION_TYPE) +# the number of the pre release (alpha1, beta3, rc7, etc.) +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}") + ## For debugging the PYTHON* variables message("PYTHONLIBS_FOUND: " ${PYTHONLIBS_FOUND}) message("PYTHON_LIBRARIES: " ${PYTHON_LIBRARIES}) @@ -247,6 +266,22 @@ if(CMAKE_HOST_APPLE) endif() endif() +# Build with Address sanitizer enabled if requested. This may break things, so use at your own risk. +if (SANITIZE_ADDRESS AND NOT MSVC) + # Currently this does not check that the clang / gcc version used supports Address sanitizer, + # so once again, use at your own risk. + add_compile_options("-fsanitize=address" "-g" "-fno-omit-frame-pointer") + # We need to add the sanitize address option to all linked executables / shared libraries + # so that proper sanitizer symbols are linked in. + # + # Note that when running tests, you may need to set an additional environment variable + # in set_tests_properties for shiboken2 / pyside tests, or exported in your shell. Address + # sanitizer will tell you what environment variable needs to be exported. For example: + # export DYLD_INSERT_LIBRARIES=/Applications/Xcode.app/Contents/Developer/Toolchains/ + # ./XcodeDefault.xctoolchain/usr/lib/clang/8.1.0/lib/darwin/libclang_rt.asan_osx_dynamic.dylib + set(CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_STANDARD_LIBRARIES} -fsanitize=address") +endif() + add_subdirectory(ApiExtractor) set(generator_plugin_DIR ${LIB_INSTALL_DIR}/generatorrunner${generator_SUFFIX}) @@ -366,16 +401,3 @@ else() endif() add_subdirectory(data) - -# dist target -set(ARCHIVE_NAME ${CMAKE_PROJECT_NAME}-${shiboken2_VERSION}) -add_custom_target(dist - COMMAND mkdir -p "${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}" && - git log > "${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}/ChangeLog" && - git archive --prefix=${ARCHIVE_NAME}/ HEAD --format=tar --output="${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar" && - tar -C "${CMAKE_BINARY_DIR}" --owner=root --group=root -r "${ARCHIVE_NAME}/ChangeLog" -f "${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar" && - bzip2 -f9 "${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar" && - echo "Source package created at ${CMAKE_BINARY_DIR}/${ARCHIVE_NAME}.tar.bz2." - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) - - diff --git a/sources/shiboken2/generator/qtdoc/qtdocgenerator.cpp b/sources/shiboken2/generator/qtdoc/qtdocgenerator.cpp index fb4de46d2..7cce97ae1 100644 --- a/sources/shiboken2/generator/qtdoc/qtdocgenerator.cpp +++ b/sources/shiboken2/generator/qtdoc/qtdocgenerator.cpp @@ -111,9 +111,14 @@ static int writeEscapedRstText(QTextStream &str, const String &s) { int escaped = 0; for (const QChar &c : s) { - if (c == QLatin1Char('*') || c == QLatin1Char('_')) { + switch (c.unicode()) { + case '*': + case '`': + case '_': + case '\\': str << '\\'; ++escaped; + break; } str << c; } @@ -199,7 +204,7 @@ QtXmlToSphinx::QtXmlToSphinx(QtDocGenerator* generator, const QString& doc, cons m_handlerMap.insert(QLatin1String("argument"), &QtXmlToSphinx::handleArgumentTag); m_handlerMap.insert(QLatin1String("teletype"), &QtXmlToSphinx::handleArgumentTag); m_handlerMap.insert(QLatin1String("link"), &QtXmlToSphinx::handleLinkTag); - m_handlerMap.insert(QLatin1String("inlineimage"), &QtXmlToSphinx::handleImageTag); + m_handlerMap.insert(QLatin1String("inlineimage"), &QtXmlToSphinx::handleInlineImageTag); m_handlerMap.insert(QLatin1String("image"), &QtXmlToSphinx::handleImageTag); m_handlerMap.insert(QLatin1String("list"), &QtXmlToSphinx::handleListTag); m_handlerMap.insert(QLatin1String("term"), &QtXmlToSphinx::handleTermTag); @@ -365,6 +370,16 @@ QString QtXmlToSphinx::transform(const QString& doc) m_lastTagName = reader.name().toString(); } } + + if (!m_inlineImages.isEmpty()) { + // Write out inline image definitions stored in handleInlineImageTag(). + m_output << endl; + for (const InlineImage &img : qAsConst(m_inlineImages)) + m_output << ".. |" << img.tag << "| image:: " << img.href << endl; + m_output << endl; + m_inlineImages.clear(); + } + m_output.flush(); QString retval = popOutputBuffer(); Q_ASSERT(m_buffers.isEmpty()); @@ -936,22 +951,46 @@ static bool copyImage(const QString &href, const QString &docDataDir, return true; } +bool QtXmlToSphinx::copyImage(const QString &href) const +{ + QString errorMessage; + const bool result = + ::copyImage(href, m_generator->docDataDir(), m_context, + m_generator->outputDirectory(), &errorMessage); + if (!result) + qCWarning(lcShiboken, "%s", qPrintable(errorMessage)); + return result; +} + void QtXmlToSphinx::handleImageTag(QXmlStreamReader& reader) { - QXmlStreamReader::TokenType token = reader.tokenType(); - if (token == QXmlStreamReader::StartElement) { - QString href = reader.attributes().value(QLatin1String("href")).toString(); - QString errorMessage; - if (!copyImage(href,m_generator->docDataDir(), m_context, - m_generator->outputDirectory(), &errorMessage)) { - qCWarning(lcShiboken, "%s", qPrintable(errorMessage)); - } + if (reader.tokenType() != QXmlStreamReader::StartElement) + return; + const QString href = reader.attributes().value(QLatin1String("href")).toString(); + if (copyImage(href)) + m_output << INDENT << ".. image:: " << href << endl << endl; +} - if (reader.name() == QLatin1String("image")) - m_output << INDENT << ".. image:: " << href << endl << endl; - else - m_output << ".. image:: " << href << ' '; - } +void QtXmlToSphinx::handleInlineImageTag(QXmlStreamReader& reader) +{ + if (reader.tokenType() != QXmlStreamReader::StartElement) + return; + const QString href = reader.attributes().value(QLatin1String("href")).toString(); + if (!copyImage(href)) + return; + // Handle inline images by substitution references. Insert a unique tag + // enclosed by '|' and define it further down. Determine tag from the base + //file name with number. + QString tag = href; + int pos = tag.lastIndexOf(QLatin1Char('/')); + if (pos != -1) + tag.remove(0, pos + 1); + pos = tag.indexOf(QLatin1Char('.')); + if (pos != -1) + tag.truncate(pos); + tag += QString::number(m_inlineImages.size() + 1); + m_inlineImages.append(InlineImage{tag, href}); + m_output << '|' << tag << '|' << ' '; } void QtXmlToSphinx::handleRawTag(QXmlStreamReader& reader) @@ -1323,7 +1362,7 @@ void QtDocGenerator::generateClass(QTextStream &s, GeneratorContext &classContex s << className << endl; s << Pad('*', className.count()) << endl << endl; - s << ".. inheritance-diagram:: " << className << endl + s << ".. inheritance-diagram:: " << getClassTargetFullName(metaClass, true) << endl << " :parts: 2" << endl << endl; // TODO: This would be a parameter in the future... diff --git a/sources/shiboken2/generator/qtdoc/qtdocgenerator.h b/sources/shiboken2/generator/qtdoc/qtdocgenerator.h index 1977f3019..af26b7fab 100644 --- a/sources/shiboken2/generator/qtdoc/qtdocgenerator.h +++ b/sources/shiboken2/generator/qtdoc/qtdocgenerator.h @@ -49,6 +49,12 @@ class QtDocGenerator; class QtXmlToSphinx { public: + struct InlineImage + { + QString tag; + QString href; + }; + struct TableCell { short rowSpan; @@ -127,6 +133,7 @@ private: void handleDotsTag(QXmlStreamReader& reader); void handleLinkTag(QXmlStreamReader& reader); void handleImageTag(QXmlStreamReader& reader); + void handleInlineImageTag(QXmlStreamReader& reader); void handleListTag(QXmlStreamReader& reader); void handleTermTag(QXmlStreamReader& reader); void handleSuperScriptTag(QXmlStreamReader& reader); @@ -168,6 +175,7 @@ private: bool m_insideItalic; QString m_lastTagName; QString m_opened_anchor; + QVector<InlineImage> m_inlineImages; QString readFromLocations(const QStringList &locations, const QString &path, const QString &identifier, QString *errorMessage); @@ -176,6 +184,7 @@ private: void pushOutputBuffer(); QString popOutputBuffer(); void writeTable(Table& table); + bool copyImage(const QString &href) const; }; inline QTextStream& operator<<(QTextStream& s, const QtXmlToSphinx& xmlToSphinx) diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp index 7a688d1da..be42adb0f 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp @@ -4471,6 +4471,7 @@ void CppGenerator::writeEnumInitialization(QTextStream& s, const AbstractMetaEnu const AbstractMetaClass* enclosingClass = getProperEnclosingClassForEnum(cppEnum); const AbstractMetaClass* upper = enclosingClass ? enclosingClass->enclosingClass() : 0; bool hasUpperEnclosingClass = upper && upper->typeEntry()->codeGeneration() != TypeEntry::GenerateForSubclass; + const EnumTypeEntry *enumTypeEntry = cppEnum->typeEntry(); QString enclosingObjectVariable; if (enclosingClass) enclosingObjectVariable = QLatin1Char('&') + cpythonTypeName(enclosingClass); @@ -4483,14 +4484,17 @@ void CppGenerator::writeEnumInitialization(QTextStream& s, const AbstractMetaEnu s << (cppEnum->isAnonymous() ? "anonymous enum identified by enum value" : "enum"); s << " '" << cppEnum->name() << "'." << endl; + QString enumVarTypeObj; if (!cppEnum->isAnonymous()) { - FlagsTypeEntry* flags = cppEnum->typeEntry()->flags(); + FlagsTypeEntry* flags = enumTypeEntry->flags(); if (flags) { s << INDENT << cpythonTypeNameExt(flags) << " = PySide::QFlags::create(\"" << flags->flagsName() << "\", &" << cpythonEnumName(cppEnum) << "_as_number);" << endl; } - s << INDENT << cpythonTypeNameExt(cppEnum->typeEntry()) << " = Shiboken::Enum::"; + enumVarTypeObj = cpythonTypeNameExt(enumTypeEntry); + + s << INDENT << enumVarTypeObj << " = Shiboken::Enum::"; s << ((enclosingClass || hasUpperEnclosingClass) ? "createScopedEnum" : "createGlobalEnum"); s << '(' << enclosingObjectVariable << ',' << endl; { @@ -4512,7 +4516,7 @@ void CppGenerator::writeEnumInitialization(QTextStream& s, const AbstractMetaEnu const AbstractMetaEnumValueList &enumValues = cppEnum->values(); for (const AbstractMetaEnumValue *enumValue : enumValues) { - if (cppEnum->typeEntry()->isEnumValueRejected(enumValue->name())) + if (enumTypeEntry->isEnumValueRejected(enumValue->name())) continue; QString enumValueText; @@ -4528,7 +4532,8 @@ void CppGenerator::writeEnumInitialization(QTextStream& s, const AbstractMetaEnu enumValueText += QString::number(enumValue->value()); } - if (cppEnum->isAnonymous()) { + switch (enumTypeEntry->enumKind()) { + case EnumTypeEntry::AnonymousEnum: if (enclosingClass || hasUpperEnclosingClass) { s << INDENT << '{' << endl; { @@ -4551,15 +4556,27 @@ void CppGenerator::writeEnumInitialization(QTextStream& s, const AbstractMetaEnu s << INDENT << "return " << m_currentErrorCode << ';' << endl; } } - } else { + break; + case EnumTypeEntry::CEnum: { s << INDENT << "if (!Shiboken::Enum::"; s << ((enclosingClass || hasUpperEnclosingClass) ? "createScopedEnumItem" : "createGlobalEnumItem"); - s << '(' << cpythonTypeNameExt(cppEnum->typeEntry()) << ',' << endl; + s << '(' << enumVarTypeObj << ',' << endl; Indentation indent(INDENT); s << INDENT << enclosingObjectVariable << ", \"" << enumValue->name() << "\", "; s << enumValueText << "))" << endl; s << INDENT << "return " << m_currentErrorCode << ';' << endl; } + break; + case EnumTypeEntry::EnumClass: { + s << INDENT << "if (!Shiboken::Enum::createScopedEnumItem(" + << enumVarTypeObj << ',' << endl; + Indentation indent(INDENT); + s << INDENT << enumVarTypeObj<< ", \"" << enumValue->name() << "\", " + << enumValueText << "))" << endl + << INDENT << "return " << m_currentErrorCode << ';' << endl; + } + break; + } } writeEnumConverterInitialization(s, cppEnum); diff --git a/sources/shiboken2/libshiboken/sbkenum.cpp b/sources/shiboken2/libshiboken/sbkenum.cpp index a62448aa6..c817a21de 100644 --- a/sources/shiboken2/libshiboken/sbkenum.cpp +++ b/sources/shiboken2/libshiboken/sbkenum.cpp @@ -492,11 +492,11 @@ bool createGlobalEnumItem(PyTypeObject* enumType, PyObject* module, const char* return false; } -bool createScopedEnumItem(PyTypeObject* enumType, SbkObjectType* scope, const char* itemName, long itemValue) +bool createScopedEnumItem(PyTypeObject *enumType, PyTypeObject *scope, + const char *itemName, long itemValue) { - PyObject* enumItem = createEnumItem(enumType, itemName, itemValue); - if (enumItem) { - if (PyDict_SetItemString(scope->super.ht_type.tp_dict, itemName, enumItem) < 0) + if (PyObject *enumItem = createEnumItem(enumType, itemName, itemValue)) { + if (PyDict_SetItemString(scope->tp_dict, itemName, enumItem) < 0) return false; Py_DECREF(enumItem); return true; @@ -504,6 +504,11 @@ bool createScopedEnumItem(PyTypeObject* enumType, SbkObjectType* scope, const ch return false; } +bool createScopedEnumItem(PyTypeObject* enumType, SbkObjectType* scope, const char* itemName, long itemValue) +{ + return createScopedEnumItem(enumType, &scope->super.ht_type, itemName, itemValue); +} + PyObject* newItem(PyTypeObject* enumType, long itemValue, const char* itemName) { bool newValue = true; diff --git a/sources/shiboken2/libshiboken/sbkenum.h b/sources/shiboken2/libshiboken/sbkenum.h index 4b572dbcc..b01114ba6 100644 --- a/sources/shiboken2/libshiboken/sbkenum.h +++ b/sources/shiboken2/libshiboken/sbkenum.h @@ -95,6 +95,8 @@ namespace Enum */ LIBSHIBOKEN_API bool createGlobalEnumItem(PyTypeObject* enumType, PyObject* module, const char* itemName, long itemValue); /// This function does the same as createGlobalEnumItem, but adds the enum to a Shiboken type or namespace. + LIBSHIBOKEN_API bool createScopedEnumItem(PyTypeObject *enumType, PyTypeObject *scope, + const char *itemName, long itemValue); LIBSHIBOKEN_API bool createScopedEnumItem(PyTypeObject* enumType, SbkObjectType* scope, const char* itemName, long itemValue); LIBSHIBOKEN_API PyObject* newItem(PyTypeObject* enumType, long itemValue, const char* itemName = 0); diff --git a/sources/shiboken2/shiboken_version.py b/sources/shiboken2/shiboken_version.py new file mode 100644 index 000000000..3a678cd14 --- /dev/null +++ b/sources/shiboken2/shiboken_version.py @@ -0,0 +1,49 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of PySide2. +## +## $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$ +## +############################################################################# + +major_version = "5" +minor_version = "11" +patch_version = "0" +pre_release_version_type = "a" # e.g. "a", "b", "rc". +pre_release_version = "1" # e.g "1", "2", (which means "beta1", "beta2", if type is "b") + +if __name__ == '__main__': + # Used by CMake. + print('{0};{1};{2};{3};{4}'.format(major_version, minor_version, patch_version, + pre_release_version_type, pre_release_version)) diff --git a/sources/shiboken2/tests/libsample/objecttype.h b/sources/shiboken2/tests/libsample/objecttype.h index 752659488..91fb45515 100644 --- a/sources/shiboken2/tests/libsample/objecttype.h +++ b/sources/shiboken2/tests/libsample/objecttype.h @@ -45,6 +45,12 @@ struct Event SOME_EVENT, ANY_EVENT }; + + enum class EventTypeClass { + Value1, + Value2 + }; + Event(EventType eventType) : m_eventType(eventType) {} EventType eventType() { return m_eventType; } private: diff --git a/sources/shiboken2/tests/samplebinding/enum_test.py b/sources/shiboken2/tests/samplebinding/enum_test.py index 6468d3cc4..711215c35 100644 --- a/sources/shiboken2/tests/samplebinding/enum_test.py +++ b/sources/shiboken2/tests/samplebinding/enum_test.py @@ -110,6 +110,11 @@ class EnumTest(unittest.TestCase): self.assertEqual(SampleNamespace.AnonymousClassEnum_Value0, 0) self.assertEqual(SampleNamespace.AnonymousClassEnum_Value1, 1) + def testEnumClasses(self): + # C++ 11: values of enum classes need to be fully qualified to match C++ + sum = Event.EventTypeClass.Value1 + Event.EventTypeClass.Value2 + self.assertEqual(sum, 1) + def testEnumTpPrintImplementation(self): '''Without SbkEnum.tp_print 'print' returns the enum represented as an int.''' tmpfile = createTempFile() diff --git a/sources/shiboken2/tests/samplebinding/typesystem_sample.xml b/sources/shiboken2/tests/samplebinding/typesystem_sample.xml index ffb5c976f..5a12eeccd 100644 --- a/sources/shiboken2/tests/samplebinding/typesystem_sample.xml +++ b/sources/shiboken2/tests/samplebinding/typesystem_sample.xml @@ -798,6 +798,7 @@ <value-type name="Event"> <enum-type name="EventType"/> + <enum-type name="EventTypeClass" class="yes"/> </value-type> <value-type name="BlackBox"> @@ -323,7 +323,7 @@ def makefile(dst, content=None, vars=None): def copydir(src, dst, filter=None, ignore=None, force=True, recursive=True, vars=None, - dir_filter_function=None, force_copy_symlinks=False): + dir_filter_function=None, file_filter_function=None, force_copy_symlinks=False): if vars is not None: src = src.format(**vars) @@ -357,10 +357,12 @@ def copydir(src, dst, filter=None, ignore=None, force=True, recursive=True, vars if recursive: results.extend( copydir(srcname, dstname, filter, ignore, force, recursive, - vars, dir_filter_function, force_copy_symlinks)) + vars, dir_filter_function, file_filter_function, + force_copy_symlinks)) else: - if (filter is not None and not filter_match(name, filter)) or \ - (ignore is not None and filter_match(name, ignore)): + if (file_filter_function is not None and not file_filter_function(name, srcname)) \ + or (filter is not None and not filter_match(name, filter)) \ + or (ignore is not None and filter_match(name, ignore)): continue if not os.path.exists(dst): os.makedirs(dst) @@ -983,3 +985,28 @@ def rpathsHasOrigin(rpaths): if match: return True return False + +def memoize(function): + """ Decorator to wrap a function with a memoizing callable. + It returns cached values when the wrapped function is called with the same arguments. + """ + memo = {} + def wrapper(*args): + if args in memo: + return memo[args] + else: + rv = function(*args) + memo[args] = rv + return rv + return wrapper + +def get_python_dict(python_script_path): + try: + with open(python_script_path) as f: + python_dict = {} + code = compile(f.read(), python_script_path, 'exec') + exec(code, {}, python_dict) + return python_dict + except IOError as e: + print("get_python_dict: Couldn't get dict from python file: {}.".format(python_script_path)) + raise |