diff options
42 files changed, 1279 insertions, 406 deletions
diff --git a/.gitignore b/.gitignore index b8977159c..c4bc8c8f8 100644 --- a/.gitignore +++ b/.gitignore @@ -69,7 +69,7 @@ develop-eggs/ downloads/ eggs/ .eggs/ -lib/ +/lib/ lib64/ parts/ sdist/ diff --git a/README.pyside2.md b/README.pyside2.md index f1bd8aaa4..94cdd16e3 100644 --- a/README.pyside2.md +++ b/README.pyside2.md @@ -69,3 +69,9 @@ our [guidelines](https://wiki.qt.io/Qt_for_Python/Reporting_Bugs). Check *#qt-pyside*, our official IRC channel on FreeNode, or contact us via our [mailing list](http://lists.qt-project.org/mailman/listinfo/pyside). + +### Licensing + +PySide2 is available under both Open Source (LGPLv3/GPLv2) and commercial license. +Using PyPi is the recommended installation source, because the content of the wheels is valid for both cases. +For more information, refer to the [Qt Licensing page](https://www.qt.io/licensing/). diff --git a/build_scripts/config.py b/build_scripts/config.py index cad4e2f57..25036a65f 100644 --- a/build_scripts/config.py +++ b/build_scripts/config.py @@ -174,6 +174,7 @@ class Config(object): 'Environment :: Win32 (MS Windows)', 'Intended Audience :: Developers', 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)', + 'License :: Other/Proprietary License', 'Operating System :: MacOS :: MacOS X', 'Operating System :: POSIX', 'Operating System :: POSIX :: Linux', diff --git a/build_scripts/main.py b/build_scripts/main.py index 4d9f95b14..f853bc899 100644 --- a/build_scripts/main.py +++ b/build_scripts/main.py @@ -527,7 +527,13 @@ class PysideBuild(_build): self.py_scripts_dir = py_scripts_dir if py_libdir is None or not os.path.exists(py_libdir): if sys.platform == "win32": - py_libdir = os.path.join(py_prefix, "libs") + # For virtual environments on Windows, the py_prefix will contain a path pointing + # to it, instead of the system Python installation path. + # Since INCLUDEPY contains a path to the system location, we use the same base + # directory to define the py_libdir variable. + py_libdir = os.path.join(os.path.dirname(py_include_dir), "libs") + if not os.path.isdir(py_libdir): + raise DistutilsSetupError("Failed to locate the 'libs' directory") else: py_libdir = os.path.join(py_prefix, "lib") if py_include_dir is None or not os.path.exists(py_include_dir): @@ -705,7 +711,7 @@ class PysideBuild(_build): log.info("Creating install folder {}...".format(self.install_dir)) os.makedirs(self.install_dir) - if not (OPTION_ONLYPACKAGE + if (not OPTION_ONLYPACKAGE and not config.is_internal_shiboken_generator_build_and_part_of_top_level_all()): # Build extensions for ext in config.get_buildable_extensions(): @@ -1056,7 +1062,7 @@ class PysideBuild(_build): OPTION_MACOS_SYSROOT)) else: latest_sdk_path = run_process_output(['xcrun', - '--show-sdk-path']) + '--sdk', 'macosx', '--show-sdk-path']) if latest_sdk_path: latest_sdk_path = latest_sdk_path[0] cmake_cmd.append("-DCMAKE_OSX_SYSROOT={}".format( diff --git a/build_scripts/utils.py b/build_scripts/utils.py index b34d37123..85a2f9e36 100644 --- a/build_scripts/utils.py +++ b/build_scripts/utils.py @@ -1089,13 +1089,6 @@ def get_python_dict(python_script_path): "file: {}.".format(python_script_path)) raise -def install_pip_wheel_package(env_pip): - # Need to install an unreleased wheel version, due to a bug that - # will generate a wheel which will not be installable. - # See https://github.com/pypa/wheel/issues/263 - wheel_url = "git+https://github.com/pypa/wheel.git@fbf3e3ada64d36ca7bb9c1422f5a1ccdba7e4dcf" - install_pip_package_from_url_specifier(env_pip, wheel_url) - def install_pip_package_from_url_specifier(env_pip, url, upgrade=True): args = [env_pip, "install", url] if upgrade: @@ -1148,7 +1141,8 @@ def run_instruction(instruction, error, initial_env=None): def acceptCITestConfiguration(hostOS, hostOSVer, targetArch, compiler): # Disable unsupported CI configs for now # NOTE: String must match with QT CI's storagestruct thrift - if hostOSVer in ["WinRT_10", "WebAssembly", "Ubuntu_18_04"]: + if hostOSVer in ["WinRT_10", "WebAssembly", "Ubuntu_18_04", "Android_ANY"] \ + or hostOSVer.startswith("SLES_"): print("Disabled " + hostOSVer + " from Coin configuration") return False # With 5.11 CI will create two sets of release binaries, one with msvc 2015 and one with msvc 2017 diff --git a/build_scripts/wheel_override.py b/build_scripts/wheel_override.py index 3a5c8164b..025b7ebc2 100644 --- a/build_scripts/wheel_override.py +++ b/build_scripts/wheel_override.py @@ -157,8 +157,17 @@ class PysideBuildWheel(_bdist_wheel): else: abi_tag = str(get_abi_tag()).lower() tag = (impl, abi_tag, plat_name) - supported_tags = pep425tags.get_supported( - supplied_platform=plat_name if self.plat_name_supplied else None) + try: + supported_tags = pep425tags.get_supported( + supplied_platform=plat_name if self.plat_name_supplied else None) + except TypeError: + # This was breaking the CI, specifically the: + # OpenSUSE 15 x86_64 using ICC + # Some versions of Python 2.7 require an argument called + # 'archive_root' which doesn't exist on 3, so we set it to + # 'None' for those version (e.g.: Python 2.7.14) + supported_tags = pep425tags.get_supported(None, + supplied_platform=plat_name if self.plat_name_supplied else None) # XXX switch to this alternate implementation for non-pure: if (self.py_limited_api) or (plat_name in ('manylinux1_x86_64') and sys.version_info[0] == 2): return tag diff --git a/coin/instructions/common_environment.yaml b/coin/instructions/common_environment.yaml new file mode 100644 index 000000000..2c6603334 --- /dev/null +++ b/coin/instructions/common_environment.yaml @@ -0,0 +1,117 @@ +type: Group +instructions: + - type: EnvironmentVariable + variableName: TARGET_ARCHITECTURE + variableValue: amd64 + enable_if: + condition: and + conditions: + - condition: property + property: target.arch + equals_value: X86_64 + - condition: property + property: host.os + equals_value: Windows + - type: EnvironmentVariable + variableName: TARGET_ARCHITECTURE + variableValue: amd64_x86 + enable_if: + condition: and + conditions: + - condition: property + property: target.arch + equals_value: X86 + - condition: property + property: host.os + equals_value: Windows + - type: EnvironmentVariable + variableName: CI_TARGET_ARCHITECTURE + variableValue: X86_64 + enable_if: + condition: property + property: target.arch + equals_value: X86_64 + - type: EnvironmentVariable + variableName: CI_TARGET_ARCHITECTURE + variableValue: X86 + enable_if: + condition: property + property: target.arch + equals_value: X86 + - type: EnvironmentVariable + variableName: CI_PACKAGING_FEATURE + variableValue: "--packaging" + enable_if: + condition: property + property: features + contains_value: Packaging + - type: EnvironmentVariable + variableName: CI_OS + variableValue: "MacOS" + enable_if: + condition: property + property: host.os + equals_value: MacOS + - type: EnvironmentVariable + variableName: CI_OS + variableValue: "Linux" + enable_if: + condition: property + property: host.os + equals_value: Linux + - type: EnvironmentVariable + variableName: CI_OS + variableValue: "Windows" + enable_if: + condition: property + property: host.os + equals_value: Windows + - type: EnvironmentVariable + variableName: MAKEFLAGS + variableValue: "" + enable_if: + condition: property + property: host.os + equals_value: Windows + - type: AppendToEnvironmentVariable + variableName: PATH + variableValue: ;%CI_JOM_PATH% + enable_if: + condition: property + property: host.os + equals_value: Windows + - type: PrependToEnvironmentVariable + variableName: PATH + variableValue: "\\Users\\qt\\work\\install\\bin;" + enable_if: + condition: property + property: host.os + equals_value: Windows + - type: EnvironmentVariable + variableName: ICC64_18_LDLP + variableValue: /opt/intel/lib/intel64 + enable_if: + condition: property + property: target.compiler + equals_value: ICC_18 + - type: EnvironmentVariable + variableName: ICC64_18_PATH # Seems a bit hard to maintain + variableValue: /opt/intel/compilers_and_libraries_2018.1.163/linux/bin/intel64:/opt/intel/bin + enable_if: + condition: property + property: target.compiler + equals_value: ICC_18 + - type: AppendToEnvironmentVariable + variableName: LD_LIBRARY_PATH + variableValue: :/opt/intel/lib/intel64 + enable_if: + condition: property + property: target.compiler + equals_value: ICC_18 + - type: AppendToEnvironmentVariable + variableName: PATH + variableValue: ":{{.Env.ICC64_18_PATH}}" + enable_if: + condition: property + property: target.compiler + equals_value: ICC_18 diff --git a/coin/instructions/execute_build_instructions.yaml b/coin/instructions/execute_build_instructions.yaml new file mode 100644 index 000000000..831783a73 --- /dev/null +++ b/coin/instructions/execute_build_instructions.yaml @@ -0,0 +1,56 @@ +type: Group +instructions: + - type: ExecuteCommand + command: "python -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=/Users/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}} --phase=ALL" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 300 + enable_if: + condition: property + property: host.os + equals_value: MacOS + userMessageOnFailure: > + Failed to execute build instructions on osx + - type: ExecuteCommand + command: "python -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=/home/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}} --phase=ALL" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 300 + enable_if: + condition: property + property: host.os + equals_value: Linux + userMessageOnFailure: > + Failed to execute build instructions on Linux + - type: ExecuteCommand + command: "c:\\users\\qt\\MSVC.bat python -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}} --phase=BUILD" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 300 + enable_if: + condition: property + property: host.os + equals_value: Windows + userMessageOnFailure: > + Failed to execute build instructions on Windows + - type: SignPackage + directory: "{{.AgentWorkingDir}}\\pyside" + maxTimeInSeconds: 1200 + maxTimeBetweenOutput: 1200 + enable_if: + condition: property + property: host.os + equals_value: Windows + - type: ChangeDirectory + directory: "{{.AgentWorkingDir}}\\pyside\\pyside-setup" + enable_if: + condition: property + property: host.os + equals_value: Windows + - type: ExecuteCommand + command: "c:\\users\\qt\\MSVC.bat python -u coin_build_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}} --phase=WHEEL" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 300 + enable_if: + condition: property + property: host.os + equals_value: Windows + userMessageOnFailure: > + Failed to create wheels for Windows diff --git a/coin/instructions/execute_license_check.yaml b/coin/instructions/execute_license_check.yaml new file mode 100644 index 000000000..3a78a0aab --- /dev/null +++ b/coin/instructions/execute_license_check.yaml @@ -0,0 +1,31 @@ +type: Group +instructions: + - type: ChangeDirectory + directory: "{{.AgentWorkingDir}}" + - type: InstallSourceArchive + maxTimeInSeconds: 600 + maxTimeBetweenOutput: 600 + project: qt/qtbase + ref: 5.14 + directory: qt/qtbase + userMessageOnFailure: > + Could not install source archive. Please investigate why. + - type: InstallSourceArchive + maxTimeInSeconds: 600 + maxTimeBetweenOutput: 600 + project: qt/qtqa + ref: master + directory: qt/qtqa-latest + userMessageOnFailure: > + Could not install source archive. Please investigate why. + - type: EnvironmentVariable + variableName: QT_MODULE_TO_TEST + variableValue: pyside/pyside-setup + userMessageOnFailure: > + Failed to set environment variable. This should not happen. + - type: ExecuteCommand + command: perl qt/qtqa-latest/tests/prebuild/license/tst_licenses.pl + maxTimeInSeconds: 7200 + maxTimeBetweenOutput: 120 + userMessageOnFailure: > + Failed to run license check script. diff --git a/coin/instructions/execute_test_instructions.yaml b/coin/instructions/execute_test_instructions.yaml new file mode 100644 index 000000000..87abb476c --- /dev/null +++ b/coin/instructions/execute_test_instructions.yaml @@ -0,0 +1,39 @@ +type: Group +enable_if: + condition: property + property: features + not_contains_value: LicenseCheck +instructions: + - type: EnvironmentVariable + variableName: QTEST_ENVIRONMENT + variableValue: "ci" + - type: ExecuteCommand + command: "python -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=/Users/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}}" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 300 + enable_if: + condition: property + property: host.os + equals_value: MacOS + userMessageOnFailure: > + Failed to execute test instructions on osx + - type: ExecuteCommand + command: "python -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=/home/qt/work/install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}}" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 300 + enable_if: + condition: property + property: host.os + equals_value: Linux + userMessageOnFailure: > + Failed to execute test instructions on Linux + - type: ExecuteCommand + command: "c:\\users\\qt\\MSVC.bat python -u coin_test_instructions.py --os={{.Env.CI_OS}} {{.Env.CI_PACKAGING_FEATURE}} --instdir=\\Users\\qt\\work\\install --targetOs={{.Env.CI_OS}} --hostArch=X86_64 --targetArch={{.Env.CI_TARGET_ARCHITECTURE}}" + maxTimeInSeconds: 14400 + maxTimeBetweenOutput: 300 + enable_if: + condition: property + property: host.os + equals_value: Windows + userMessageOnFailure: > + Failed to execute test instructions on 64 bit Windows diff --git a/coin/instructions/find_path_to_msvc_compiler.yaml b/coin/instructions/find_path_to_msvc_compiler.yaml new file mode 100644 index 000000000..8f9bed82f --- /dev/null +++ b/coin/instructions/find_path_to_msvc_compiler.yaml @@ -0,0 +1,28 @@ +type: Group +enable_if: + condition: property + property: host.os + equals_value: Windows +instructions: + - type: EnvironmentVariable + variableName: VC_SCRIPT + variableValue: "%ProgramFiles(x86)%\\Microsoft Visual Studio\\2017\\Professional\\VC\\Auxiliary\\Build\\vcvarsall.bat" + enable_if: + condition: property + property: host.compiler + equals_value: MSVC2017 + - type: EnvironmentVariable + variableName: VC_SCRIPT + variableValue: "%ProgramFiles(x86)%\\Microsoft Visual Studio\\2019\\Professional\\VC\\Auxiliary\\Build\\vcvarsall.bat" + enable_if: + condition: property + property: host.compiler + equals_value: MSVC2019 + - type: WriteFile + fileContents: "call \"{{.Env.VC_SCRIPT}}\" {{.Env.TARGET_ARCHITECTURE}} \r\ncmd /c %*" + filename: "c:\\users\\qt\\MSVC.bat" + fileMode: 420 + enable_if: + condition: property + property: host.os + equals_value: Windows diff --git a/coin/instructions/relocate_pyside.yaml b/coin/instructions/relocate_pyside.yaml new file mode 100644 index 000000000..afab83c70 --- /dev/null +++ b/coin/instructions/relocate_pyside.yaml @@ -0,0 +1,57 @@ +type: Group +enable_if: + condition: property + property: features + not_contains_value: LicenseCheck +instructions: + - type: ChangeDirectory + directory: "{{.AgentWorkingDir}}/pyside" + maxTimeInSeconds: 300 + maxTimeBetweenOutput: 120 + userMessageOnFailure: > + Failed to change to pyside dir + - type: ExecuteCommand + command: c:\\users\\qt\\MSVC.bat rd /s /q pyside-setup + maxTimeInSeconds: 300 + maxTimeBetweenOutput: 120 + enable_if: + condition: property + property: host.os + equals_value: Windows + userMessageOnFailure: > + Failed to remove pyside-setup dir + - type: ExecuteCommand + command: /bin/rm -rf pyside-setup + maxTimeInSeconds: 300 + maxTimeBetweenOutput: 120 + enable_if: + condition: property + property: host.os + not_equals_value: Windows + userMessageOnFailure: > + Failed to remove pyside-setup dir + - type: InstallBinaryArchive + relativeStoragePath: "{{.Env.MODULE_ARTIFACTS_RELATIVE_STORAGE_PATH}}/artifacts.tar.gz" + directory: "pyside" + maxTimeInSeconds: 1200 + maxTimeBetweenOutput: 1200 + - type: ChangeDirectory + directory: "{{.AgentWorkingDir}}\\pyside\\pyside-setup" + maxTimeInSeconds: 7200 + maxTimeBetweenOutput: 120 + enable_if: + condition: property + property: host.os + equals_value: Windows + userMessageOnFailure: > + Failed to install binary artifacts + - type: ChangeDirectory + directory: "{{.AgentWorkingDir}}/pyside/pyside-setup" + maxTimeInSeconds: 7200 + maxTimeBetweenOutput: 120 + enable_if: + condition: property + property: host.os + not_equals_value: Windows + userMessageOnFailure: > + Failed to change to pyside-setup dir diff --git a/coin/module_config.yaml b/coin/module_config.yaml new file mode 100644 index 000000000..772166fb5 --- /dev/null +++ b/coin/module_config.yaml @@ -0,0 +1,68 @@ +version: 2 +accept_configuration: + condition: or + conditions: + - condition: and + conditions: + - condition: property # No cross compilation + property: host.os + equals_property: target.os + - condition: property # qtnamespace is not supported + property: configureArgs + not_contains_value: -qtnamespace + - condition: property # no-widgets is notsupported + property: configureArgs + not_contains_value: -no-widgets + - condition: property # no-gui is not supported + property: configureArgs + not_contains_value: -no-gui + - condition: property # Following configs are not supported + property: target.osVersion + not_in_values: [OPENSUSE_13_01, QEMU, WebAssembly, Ubuntu_18_04, SLES_12, SLES_15] + - condition: property # MibnGW and msvc2015 are not supported + property: target.compiler + not_in_values: [Mingw73, MSVC2015] + - condition: and + conditions: + - condition: property + property: target.osVersion + equals_value: Ubuntu_18_04 + - condition: property + property: features + contains_value: LicenseCheck + +upload_pyside: &upload_pyside + type: Group + instructions: + - type: UploadArtifact + archiveDirectory: "{{.AgentWorkingDir}}\\pyside" + transferType: UploadModuleBuildArtifact + maxTimeInSeconds: 1200 + maxTimeBetweenOutput: 1200 + enable_if: + condition: property + property: host.os + equals_value: Windows + - type: UploadArtifact + archiveDirectory: "{{.AgentWorkingDir}}/pyside" + transferType: UploadModuleBuildArtifact + maxTimeInSeconds: 1200 + maxTimeBetweenOutput: 1200 + enable_if: + condition: property + property: host.os + not_equals_value: Windows + +instructions: + LicenseCheck: + - !include "{{pyside/pyside-setup}}/execute_license_check.yaml" + Build: + - !include "{{pyside/pyside-setup}}/common_environment.yaml" + - !include "{{pyside/pyside-setup}}/find_path_to_msvc_compiler.yaml" + - !include "{{pyside/pyside-setup}}/execute_build_instructions.yaml" + - *upload_pyside + Test: + - !include "{{pyside/pyside-setup}}/common_environment.yaml" + - !include "{{pyside/pyside-setup}}/find_path_to_msvc_compiler.yaml" + - !include "{{pyside/pyside-setup}}/relocate_pyside.yaml" + - !include "{{pyside/pyside-setup}}/execute_test_instructions.yaml" diff --git a/coin_build_instructions.py b/coin_build_instructions.py index 9a7d6272c..e35bcda3b 100644 --- a/coin_build_instructions.py +++ b/coin_build_instructions.py @@ -39,14 +39,14 @@ from build_scripts.options import has_option from build_scripts.options import option_value from build_scripts.utils import install_pip_dependencies -from build_scripts.utils import install_pip_wheel_package from build_scripts.utils import get_qtci_virtualEnv from build_scripts.utils import run_instruction from build_scripts.utils import rmtree from build_scripts.utils import get_python_dict -from build_scripts.utils import acceptCITestConfiguration from build_scripts.utils import get_ci_qmake_path import os +import datetime +import calendar # Values must match COIN thrift CI_HOST_OS = option_value("os") @@ -57,13 +57,18 @@ CI_HOST_OS_VER = option_value("osVer") CI_ENV_INSTALL_DIR = option_value("instdir") CI_ENV_AGENT_DIR = option_value("agentdir") CI_COMPILER = option_value("compiler") -CI_INTEGRATION_ID = option_value("coinIntegrationId") +CI_INTEGRATION_ID = option_value("coinIntegrationId") or str(calendar.timegm(datetime.datetime.now().timetuple())) CI_FEATURES = [] _ci_features = option_value("features") if _ci_features is not None: for f in _ci_features.split(', '): CI_FEATURES.append(f) CI_RELEASE_CONF = has_option("packaging") +CI_TEST_PHASE = option_value("phase") +if CI_TEST_PHASE not in ["ALL", "BUILD", "WHEEL"]: + CI_TEST_PHASE = "ALL" + + def get_current_script_path(): """ Returns the absolute path containing this script. """ @@ -96,21 +101,20 @@ def is_snapshot_build(): return True return False -def call_setup(python_ver): +def call_setup(python_ver, phase): _pExe, _env, env_pip, env_python = get_qtci_virtualEnv(python_ver, CI_HOST_OS, CI_HOST_ARCH, CI_TARGET_ARCH) - rmtree(_env, True) - run_instruction(["virtualenv", "-p", _pExe, _env], "Failed to create virtualenv") - install_pip_dependencies(env_pip, ["pip", "numpy", "setuptools", "sphinx", "six"]) - install_pip_wheel_package(env_pip) + if phase in ["BUILD"]: + rmtree(_env, True) + run_instruction(["virtualenv", "-p", _pExe, _env], "Failed to create virtualenv") + install_pip_dependencies(env_pip, ["pip", "numpy", "setuptools", "sphinx", "six", "wheel"]) cmd = [env_python, "-u", "setup.py"] - if CI_RELEASE_CONF: - cmd += ["bdist_wheel", "--standalone"] - else: - cmd += ["build"] - qmake_path = get_ci_qmake_path(CI_ENV_INSTALL_DIR, CI_HOST_OS) - cmd.append(qmake_path) + if phase in ["BUILD"]: + cmd += ["build", "--standalone", "--skip-packaging"] + elif phase in ["WHEEL"] or CI_RELEASE_CONF: + cmd += ["bdist_wheel", "--reuse-build", "--standalone", "--skip-cmake", "--skip-make-install", "--only-package"] + cmd += ["--build-tests", "--parallel=4", "--verbose-build"] @@ -119,6 +123,9 @@ def call_setup(python_ver): if is_snapshot_build(): cmd += ["--snapshot-build"] + qmake_path = get_ci_qmake_path(CI_ENV_INSTALL_DIR, CI_HOST_OS) + cmd.append(qmake_path) + # Due to certain older CMake versions generating very long paths # (at least with CMake 3.6.2) when using the export() function, # pass the shorter paths option on Windows so we don't hit @@ -129,25 +136,27 @@ def call_setup(python_ver): cmd += ["--package-timestamp=" + CI_INTEGRATION_ID] env = os.environ - run_instruction(cmd, "Failed to run setup.py", initial_env=env) + run_instruction(cmd, "Failed to run setup.py for build", initial_env=env) -def run_build_instructions(): - if not acceptCITestConfiguration(CI_HOST_OS, CI_HOST_OS_VER, CI_TARGET_ARCH, CI_COMPILER): - exit() - # Remove some environment variables that impact cmake - for env_var in ['CC', 'CXX']: - if os.environ.get(env_var): - del os.environ[env_var] +def run_build_instructions(phase): # Uses default python, hopefully we have python2 installed on all hosts # Skip building using Python 2 on Windows, because of different MSVC C runtimes (VS2008 vs VS2015+) if CI_HOST_OS != "Windows": - call_setup("") - + call_setup("", phase) # In case of packaging build, we have to build also python3 wheel if CI_RELEASE_CONF and CI_HOST_OS_VER not in ["RHEL_6_6"]: - call_setup("3") + call_setup("3", phase) if __name__ == "__main__": - run_build_instructions() + # Remove some environment variables that impact cmake + for env_var in ['CC', 'CXX']: + if os.environ.get(env_var): + del os.environ[env_var] + + if CI_TEST_PHASE in ["ALL", "BUILD"]: + run_build_instructions("BUILD") + + if CI_TEST_PHASE in ["ALL", "WHEEL"]: + run_build_instructions("WHEEL") diff --git a/coin_test_instructions.py b/coin_test_instructions.py index aeedd03e7..a1c634497 100644 --- a/coin_test_instructions.py +++ b/coin_test_instructions.py @@ -39,11 +39,9 @@ from build_scripts.options import has_option from build_scripts.options import option_value from build_scripts.utils import install_pip_dependencies -from build_scripts.utils import install_pip_wheel_package from build_scripts.utils import get_qtci_virtualEnv from build_scripts.utils import run_instruction from build_scripts.utils import rmtree -from build_scripts.utils import acceptCITestConfiguration from build_scripts.utils import get_ci_qmake_path import os @@ -68,8 +66,7 @@ def call_testrunner(python_ver, buildnro): rmtree(_env, True) run_instruction(["virtualenv", "-p", _pExe, _env], "Failed to create virtualenv") # Keeping PyInstaller 3.4, because 3.5 seems to have broken our test - install_pip_dependencies(env_pip, ["pip", "numpy", "PyOpenGL", "setuptools", "six", "pyinstaller==3.4"]) - install_pip_wheel_package(env_pip) + install_pip_dependencies(env_pip, ["pip", "numpy", "PyOpenGL", "setuptools", "six", "pyinstaller==3.6", "wheel"]) cmd = [env_python, "testrunner.py", "test", "--blacklist", "build_history/blacklist.txt", "--buildno=" + buildnro] @@ -84,9 +81,6 @@ def call_testrunner(python_ver, buildnro): run_instruction(cmd, "Error while running wheel_tester.py") def run_test_instructions(): - if not acceptCITestConfiguration(CI_HOST_OS, CI_HOST_OS_VER, CI_TARGET_ARCH, CI_COMPILER): - exit() - # Remove some environment variables that impact cmake for env_var in ['CC', 'CXX']: if os.environ.get(env_var): @@ -99,7 +93,7 @@ def run_test_instructions(): call_testrunner("", str(testRun)) testRun =+ 1 # We know that second build was with python3 - if CI_RELEASE_CONF and CI_HOST_OS_VER not in ["RHEL_6_6"]: + if CI_RELEASE_CONF: call_testrunner("3", str(testRun)) if __name__ == "__main__": diff --git a/dist/changes-5.12.5 b/dist/changes-5.12.5 new file mode 100644 index 000000000..d463b8324 --- /dev/null +++ b/dist/changes-5.12.5 @@ -0,0 +1,40 @@ + +Qt for Python 5.12.5 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide2 * +**************************************************************************** + + - [PYSIDE-571] Support the qApp macro in "scriptable application" + - [PYSIDE-634] Add support for parameterNames in Signals + - [PYSIDE-951] Correct QtPrintSupport dependency + - [PYSIDE-1010] Add optional support for types in QSettings::value + - [PYSIDE-1020] Fix pyside2-uic to generate correct code for QWebview and QQuickWidget + - [PYSIDE-1028] Leave QVariantMap as a name, not a type + - [PYSIDE-1029] Fix type name for lookup of QFlags<> converters + - [PYSIDE-1032] Use default compiler on macOS + - [PYSIDE-1035] Fix pyside2-uic to generate correct code for QTableWidget + - [PYSIDE-1066] Fix Xcode sdk value embedded into PySide2 binaries + + +**************************************************************************** +* Shiboken2 * +**************************************************************************** + + - [PYSIDE-1024] shiboken: Generate code for smart pointers only within declaring package + - [PYSIDE-1037] shiboken: Allow for "auto" as target of type for CONVERTTOCPP in injected code + + diff --git a/dist/changes-5.12.6 b/dist/changes-5.12.6 new file mode 100644 index 000000000..d1eba40c2 --- /dev/null +++ b/dist/changes-5.12.6 @@ -0,0 +1,29 @@ +Qt for Python 5.12.6 is a bug-fix release. + +For more details, refer to the online documentation included in this +distribution. The documentation is also available online: + +https://doc.qt.io/qtforpython/ + +Some of the changes listed in this file include issue tracking numbers +corresponding to tasks in the Qt Bug Tracker: + +https://bugreports.qt.io/ + +Each of these identifiers can be entered in the bug tracker to obtain more +information about a particular change. + +**************************************************************************** +* PySide2 * +**************************************************************************** + + - [Fixes: PYSIDE-1007] Remove extra ref on QDataStream::setDevice + - [Fixes: PYSIDE-1051] Fix heaptype conflict with QtCore.QObject.__new__in Python 2.7 + + +**************************************************************************** +* Shiboken2 * +**************************************************************************** + + - No changes + diff --git a/sources/pyside2-tools b/sources/pyside2-tools -Subproject bd27bac457943aab1082ac1c5b3f81ef9574c67 +Subproject 3c402fef54106b65ab9c1df92c39c4626424fdd diff --git a/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml b/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml index 78a731458..20893b658 100644 --- a/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml +++ b/sources/pyside2/PySide2/QtCore/typesystem_core_common.xml @@ -2541,12 +2541,6 @@ <include file-name="QtCore/QtCore" location="global"/> </extra-includes> - <modify-function signature="setDevice(QIODevice*)"> - <modify-argument index="1"> - <parent index="this" action="add"/> - </modify-argument> - </modify-function> - <!-- ### Replaced by write<TYPE> methods --> <modify-function signature="operator>>(qint8&)" remove="all"/> <modify-function signature="operator>>(bool&)" remove="all"/> diff --git a/sources/pyside2/PySide2/QtPrintSupport/typesystem_printsupport.xml.in b/sources/pyside2/PySide2/QtPrintSupport/typesystem_printsupport.xml.in index 7949b2daa..ff078d19a 100644 --- a/sources/pyside2/PySide2/QtPrintSupport/typesystem_printsupport.xml.in +++ b/sources/pyside2/PySide2/QtPrintSupport/typesystem_printsupport.xml.in @@ -40,7 +40,6 @@ ****************************************************************************/ --> <typesystem package="PySide2.QtPrintSupport"> - <load-typesystem name="QtGui/typesystem_gui.xml" generate="no"/> <load-typesystem name="QtWidgets/typesystem_widgets.xml" generate="no"/> <load-typesystem name="QtPrintSupport/typesystem_printsupport_common.xml" generate="yes"/> </typesystem> diff --git a/sources/pyside2/PySide2/QtPrintSupport/typesystem_printsupport_common.xml b/sources/pyside2/PySide2/QtPrintSupport/typesystem_printsupport_common.xml index c11a6e046..487103875 100644 --- a/sources/pyside2/PySide2/QtPrintSupport/typesystem_printsupport_common.xml +++ b/sources/pyside2/PySide2/QtPrintSupport/typesystem_printsupport_common.xml @@ -40,6 +40,7 @@ ****************************************************************************/ --> <typesystem package="PySide2.QtPrintSupport"> + <load-typesystem name="QtWidgets/typesystem_widgets.xml" generate="no"/> <object-type name="QPageSetupDialog"> <modify-function signature="exec()" rename="exec_" allow-thread="yes"/> diff --git a/sources/pyside2/PySide2/glue/qtcore.cpp b/sources/pyside2/PySide2/glue/qtcore.cpp index 3e1bab97b..a218e433f 100644 --- a/sources/pyside2/PySide2/glue/qtcore.cpp +++ b/sources/pyside2/PySide2/glue/qtcore.cpp @@ -57,24 +57,39 @@ bool py2kStrCheck(PyObject *obj) // @snippet pystring-check // @snippet qsettings-value -QVariant out = %CPPSELF.value(%1, %2); +// If we enter the kwds, means that we have a defaultValue or +// at least a type. +// This avoids that we are passing '0' as defaultValue. +// defaultValue can also be passed as positional argument, +// not only as keyword. +QVariant out; +if (kwds || numArgs > 1) + out = %CPPSELF.value(%1, %2); +else + out = %CPPSELF.value(%1); + PyTypeObject *typeObj = reinterpret_cast<PyTypeObject*>(%PYARG_3); if (typeObj) { if (typeObj == &PyList_Type) { - QByteArrayList valuesList = out.toByteArray().split(','); - const int valuesSize = valuesList.size(); - if (valuesSize > 0) { - PyObject *list = PyList_New(valuesSize); - for (int i = 0; i < valuesSize; i++) { - PyObject *item = PyUnicode_FromString(valuesList[i].data()); - PyList_SET_ITEM(list, i, item); - Py_DECREF(item); - } - %PYARG_0 = list; + QByteArray out_ba = out.toByteArray(); + if (!out_ba.isEmpty()) { + QByteArrayList valuesList = out_ba.split(','); + const int valuesSize = valuesList.size(); + if (valuesSize > 0) { + PyObject *list = PyList_New(valuesSize); + for (int i = 0; i < valuesSize; i++) { + PyObject *item = PyUnicode_FromString(valuesList[i].data()); + PyList_SET_ITEM(list, i, item); + Py_DECREF(item); + } + %PYARG_0 = list; + } else { + %PYARG_0 = %CONVERTTOPYTHON[QVariant](out); + } } else { - %PYARG_0 = %CONVERTTOPYTHON[QVariant](out); + %PYARG_0 = PyList_New(0); } } else if (typeObj == &PyBytes_Type) { QByteArray asByteArray = out.toByteArray(); @@ -94,11 +109,13 @@ if (typeObj) { } else if (typeObj == &PyFloat_Type) { float asFloat = out.toFloat(); %PYARG_0 = PyFloat_FromDouble(asFloat); + } else if (typeObj == &PyBool_Type) { + %PYARG_0 = out.toBool() ? Py_True : Py_False; } // TODO: PyDict_Type and PyTuple_Type } else { - if (out == 0) + if (!out.isValid()) %PYARG_0 = Py_None; else %PYARG_0 = %CONVERTTOPYTHON[QVariant](out); @@ -1289,7 +1306,7 @@ Shiboken::AutoDecRef emptyTuple(PyTuple_New(0)); PyObject *pyTimer = reinterpret_cast<PyTypeObject *>(Shiboken::SbkType<QTimer>())->tp_new(Shiboken::SbkType<QTimer>(), emptyTuple, 0); reinterpret_cast<PyTypeObject *>(Shiboken::SbkType<QTimer>())->tp_init(pyTimer, emptyTuple, 0); -QTimer * timer = %CONVERTTOCPP[QTimer *](pyTimer); +auto timer = %CONVERTTOCPP[QTimer *](pyTimer); //XXX /|\ omitting this space crashes shiboken! Shiboken::AutoDecRef result( PyObject_CallMethod(pyTimer, @@ -1484,8 +1501,7 @@ if (PySide::SignalManager::registerMetaMethod(%1, signalName.mid(1).toLatin1().d if (!PyObject_TypeCheck(%1, PySideSignalInstanceTypeF())) goto Sbk_%TYPEFunc_%FUNCTION_NAME_TypeError; PySideSignalInstance *signalInstance = reinterpret_cast<PySideSignalInstance *>(%1); -QObject * sender = %CONVERTTOCPP[QObject *](PySide::Signal::getObject(signalInstance)); -//XXX /|\ omitting this space crashes shiboken! +auto sender = %CONVERTTOCPP[QObject *](PySide::Signal::getObject(signalInstance)); QSignalTransition *%0 = %CPPSELF->%FUNCTION_NAME(sender, PySide::Signal::getSignature(signalInstance),%2); %PYARG_0 = %CONVERTTOPYTHON[QSignalTransition *](%0); // @snippet qstate-addtransition-2 diff --git a/sources/pyside2/PySide2/support/generate_pyi.py b/sources/pyside2/PySide2/support/generate_pyi.py index 294cdc91b..c732227f4 100644 --- a/sources/pyside2/PySide2/support/generate_pyi.py +++ b/sources/pyside2/PySide2/support/generate_pyi.py @@ -252,6 +252,10 @@ def generate_all_pyi(outpath, options): from PySide2.support.signature import inspect from PySide2.support.signature.lib.enum_sig import HintingEnumerator + # propagate USE_PEP563 to the mapping module. + # Perhaps this can be automated? + PySide2.support.signature.mapping.USE_PEP563 = USE_PEP563 + outpath = outpath or os.path.dirname(PySide2.__file__) name_list = PySide2.__all__ if options.modules == ["all"] else options.modules errors = ", ".join(set(name_list) - set(PySide2.__all__)) diff --git a/sources/pyside2/pyside_version.py b/sources/pyside2/pyside_version.py index e253eb8fb..17a6bb515 100644 --- a/sources/pyside2/pyside_version.py +++ b/sources/pyside2/pyside_version.py @@ -39,7 +39,7 @@ major_version = "5" minor_version = "12" -patch_version = "4" +patch_version = "5" # For example: "a", "b", "rc" # (which means "alpha", "beta", "release candidate"). diff --git a/sources/pyside2/tests/QtCore/qsettings_test.py b/sources/pyside2/tests/QtCore/qsettings_test.py index 6d64b0db3..36a4c3c62 100644 --- a/sources/pyside2/tests/QtCore/qsettings_test.py +++ b/sources/pyside2/tests/QtCore/qsettings_test.py @@ -55,15 +55,55 @@ class TestQSettings(unittest.TestCase): def testDefaultValueConversion(self): settings = QSettings('foo.ini', QSettings.IniFormat) - r = settings.value('lala', 22) + settings.setValue('zero_value', 0) + settings.setValue('empty_list', []) + settings.setValue('bool1', False) + settings.setValue('bool2', True) + del settings + + # Loading values already set + settings = QSettings('foo.ini', QSettings.IniFormat) + + # Getting value that doesn't exist + r = settings.value("variable") + self.assertEqual(type(r), type(None)) + + # Handling zero value + r = settings.value('zero_value') if py3k.IS_PY3K: self.assertEqual(type(r), int) else: self.assertEqual(type(r), long) - r = settings.value('lala', 22, type=str) - self.assertEqual(type(r), str) + r = settings.value('zero_value', type=int) + self.assertEqual(type(r), int) + + # Empty list + r = settings.value('empty_list') + self.assertTrue(len(r) == 0) + self.assertEqual(type(r), list) + + r = settings.value('empty_list', type=list) + self.assertTrue(len(r) == 0) + self.assertEqual(type(r), list) + + # Booleans + r = settings.value('bool1') + self.assertEqual(type(r), bool) + + r = settings.value('bool2') + self.assertEqual(type(r), bool) + + r = settings.value('bool1', type=bool) + self.assertEqual(type(r), bool) + + r = settings.value('bool2', type=int) + self.assertEqual(type(r), int) + + r = settings.value('bool2', type=bool) + self.assertEqual(type(r), bool) + # Not set variable, but with default value r = settings.value('lala', 22, type=bytes) self.assertEqual(type(r), bytes) diff --git a/sources/pyside2/tests/pysidetest/CMakeLists.txt b/sources/pyside2/tests/pysidetest/CMakeLists.txt index 3c993cf4e..6c100475b 100644 --- a/sources/pyside2/tests/pysidetest/CMakeLists.txt +++ b/sources/pyside2/tests/pysidetest/CMakeLists.txt @@ -106,10 +106,17 @@ set_property(TARGET testbinding PROPERTY OUTPUT_NAME "testbinding${SHIBOKEN_PYTH if(WIN32) set_property(TARGET testbinding PROPERTY SUFFIX ".pyd") endif() + +if(PYTHON_LIMITED_API) + set(TESTBINDING_PYTHON_LIBS ${PYTHON_LIMITED_LIBRARIES}) +else() + set(TESTBINDING_PYTHON_LIBS ${PYTHON_LIBRARIES}) +endif() + target_link_libraries(testbinding pysidetest pyside2 - ${PYTHON_LIBRARIES} + ${TESTBINDING_PYTHON_LIBS} Shiboken2::libshiboken ${Qt5Core_LIBRARIES} ${Qt5Gui_LIBRARIES} diff --git a/sources/shiboken2/data/shiboken_helpers.cmake b/sources/shiboken2/data/shiboken_helpers.cmake index f4dd4d5dc..780509f58 100644 --- a/sources/shiboken2/data/shiboken_helpers.cmake +++ b/sources/shiboken2/data/shiboken_helpers.cmake @@ -13,8 +13,9 @@ macro(shiboken_check_if_limited_api) # On other platforms, this result is not used at all. execute_process( COMMAND ${PYTHON_EXECUTABLE} -c "if True: + import os for lib in '${PYTHON_LIBRARIES}'.split(';'): - if '/' in lib: + if '/' in lib and os.path.isfile(lib): prefix, py = lib.rsplit('/', 1) if py.startswith('python3'): print(prefix + '/python3.lib') diff --git a/sources/shiboken2/doc/typesystemvariables.rst b/sources/shiboken2/doc/typesystemvariables.rst index b1b9bbfe6..73d4dd12c 100644 --- a/sources/shiboken2/doc/typesystemvariables.rst +++ b/sources/shiboken2/doc/typesystemvariables.rst @@ -150,6 +150,8 @@ Variables since it otherwise would be indistinguishable from the pointer assignment above. + It is possible to use "auto" as type. + .. _converttopython: **%CONVERTTOPYTHON[CPPTYPE]** diff --git a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp index e36b6edc3..68bcfe508 100644 --- a/sources/shiboken2/generator/shiboken2/cppgenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/cppgenerator.cpp @@ -5387,7 +5387,7 @@ void CppGenerator::writeGetattroFunction(QTextStream &s, GeneratorContext &conte s << INDENT << "PyTypeObject *tp = Py_TYPE(self);" << endl; s << INDENT << "PyErr_Format(PyExc_AttributeError," << endl; s << INDENT << " \"'%.50s' object has no attribute '%.400s'\"," << endl; - s << INDENT << " tp->tp_name, PyBytes_AS_STRING(name));" << endl; + s << INDENT << " tp->tp_name, Shiboken::String::toCString(name));" << endl; s << INDENT << "return nullptr;" << endl; } s << INDENT << "} else {" << endl; diff --git a/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp b/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp index e41c91716..34e43a4c7 100644 --- a/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp +++ b/sources/shiboken2/generator/shiboken2/shibokengenerator.cpp @@ -2047,7 +2047,7 @@ void ShibokenGenerator::replaceConverterTypeSystemVariable(TypeSystemConverterVa QString varName = list.at(1).trimmed(); if (!varType.isEmpty()) { const QString conversionSignature = conversionType->cppSignature(); - if (varType != conversionSignature) + if (varType != QLatin1String("auto") && varType != conversionSignature) qFatal("%s", qPrintable(msgConversionTypesDiffer(varType, conversionSignature))); c << getFullTypeName(conversionType) << ' ' << varName; writeMinimalConstructorExpression(c, conversionType); diff --git a/sources/shiboken2/libshiboken/basewrapper.cpp b/sources/shiboken2/libshiboken/basewrapper.cpp index b9f6735d8..9d233b847 100644 --- a/sources/shiboken2/libshiboken/basewrapper.cpp +++ b/sources/shiboken2/libshiboken/basewrapper.cpp @@ -92,6 +92,7 @@ static PyType_Slot SbkObjectType_Type_slots[] = { {Py_tp_alloc, reinterpret_cast<void *>(PyType_GenericAlloc)}, {Py_tp_new, reinterpret_cast<void *>(SbkObjectTypeTpNew)}, {Py_tp_free, reinterpret_cast<void *>(PyObject_GC_Del)}, + {Py_tp_getset, reinterpret_cast<void *>(SbkObjectType_Type_getsetlist)}, {0, nullptr} }; static PyType_Spec SbkObjectType_Type_spec = { @@ -106,7 +107,7 @@ static PyType_Spec SbkObjectType_Type_spec = { #if PY_VERSION_HEX < 0x03000000 /***************************************************************************** * - * PYSIDE-816: Workaround for Python 2.7 + * PYSIDE-816: Workaround for Python 2.7 for SbkObjectType_TypeF(). * * This is an add-on for function typeobject.c:tp_new_wrapper from Python 2.7 . * Problem: @@ -121,9 +122,16 @@ static PyType_Spec SbkObjectType_Type_spec = { * The problem is that heap types have this unwanted dependency. * But we cannot get at static slot_tp_new, and so we have to use * the original function and patch Py_TPFLAGS_HEAPTYPE away during the call. + * + * PYSIDE-1051: The same problem holds for all dynamic metatypes generated by + * SbkObjectTypeTpNew() and all types generated by + * introduceWrapperType() . + * + * This led to a drastic overhaul of patch_tp_new_wrapper() which now adds + * the new wrapper to exactly those types which have the old wrapper. */ -static PyCFunction old_tp_new_wrapper = nullptr; +ternaryfunc old_tp_new_wrapper = nullptr; static PyObject * tp_new_wrapper(PyObject *self, PyObject *args, PyObject *kwds) @@ -136,9 +144,9 @@ tp_new_wrapper(PyObject *self, PyObject *args, PyObject *kwds) return ret; } -// This is intentionally the new docstring of Python 3.7 . +// This is intentionally the __new__ docstring of Python 3.7 . static struct PyMethodDef tp_new_methoddef[] = { - {"__new__", (PyCFunction)tp_new_wrapper, METH_VARARGS|METH_KEYWORDS, + {"__new__", reinterpret_cast<PyCFunction>(tp_new_wrapper), METH_VARARGS|METH_KEYWORDS, PyDoc_STR("__new__($type, *args, **kwargs)\n--\n\n" "Create and return a new object. " "See help(type) for accurate signature.")}, @@ -146,43 +154,39 @@ static struct PyMethodDef tp_new_methoddef[] = { }; static int -get_old_tp_new_wrapper(void) -{ - // We get the old tp_new_wrapper from any initialized type. - PyTypeObject *type = &PyType_Type; - PyObject *dict = type->tp_dict; - PyObject *key, *func = nullptr; - Py_ssize_t pos = 0; - while (PyDict_Next(dict, &pos, &key, &func)) { - char *name = PyString_AsString(key); - if (strcmp(name, "__new__") == 0) { - break; +patch_tp_new_wrapper(PyTypeObject *type) +{ + /* + * The old tp_new_wrapper is added to all types that have tp_new. + * We patch that with a version that ignores the heaptype flag. + */ + static PyObject *__new__ = nullptr; + if (old_tp_new_wrapper == nullptr) { + if ((__new__ = Py_BuildValue("s", "__new__")) == nullptr) + return -1; + PyObject *func = PyDict_GetItem(PyType_Type.tp_dict, __new__); + PyCFunctionObject *pycf_ob = reinterpret_cast<PyCFunctionObject *>(func); + old_tp_new_wrapper = reinterpret_cast<ternaryfunc>(pycf_ob->m_ml->ml_meth); + } + PyObject *mro = type->tp_mro; + Py_ssize_t i, n = PyTuple_GET_SIZE(mro); + for (i = 0; i < n; i++) { + type = reinterpret_cast<PyTypeObject *>(PyTuple_GET_ITEM(mro, i)); + PyObject *existing = PyDict_GetItem(type->tp_dict, __new__); + if (existing && PyCFunction_Check(existing) + && type->tp_flags & Py_TPFLAGS_HEAPTYPE) { + auto *pycf_ob = reinterpret_cast<PyCFunctionObject *>(existing); + auto existing_wrapper = reinterpret_cast<ternaryfunc>(pycf_ob->m_ml->ml_meth); + if (existing_wrapper == tp_new_wrapper) + break; + if (existing_wrapper == old_tp_new_wrapper) { + PyObject *ob_type = reinterpret_cast<PyObject *>(type); + Shiboken::AutoDecRef func(PyCFunction_New(tp_new_methoddef, ob_type)); + if (func.isNull() || PyDict_SetItem(type->tp_dict, __new__, func)) + return -1; + } } } - if (func == nullptr) - return -1; - PyCFunctionObject *pycf_ob = reinterpret_cast<PyCFunctionObject *>(func); - old_tp_new_wrapper = pycf_ob->m_ml->ml_meth; - return 0; -} - -static int -add_tp_new_wrapper(PyTypeObject *type) -{ - // get the original tp_new_wrapper - if (old_tp_new_wrapper == nullptr && get_old_tp_new_wrapper() < 0) - return -1; - // initialize tp_dict - if (type->tp_dict == nullptr) - type->tp_dict = PyDict_New(); - if (type->tp_dict == nullptr) - return -1; - PyObject *ob_type = reinterpret_cast<PyObject *>(type); - Shiboken::AutoDecRef func(PyCFunction_New(tp_new_methoddef, ob_type)); - if (func.isNull()) - return -1; - if (PyDict_SetItemString(type->tp_dict, "__new__", func)) - return -1; return 0; } /*****************************************************************************/ @@ -197,7 +201,7 @@ PyTypeObject *SbkObjectType_TypeF(void) PepHeapType_SIZE + sizeof(SbkObjectTypePrivate); type = reinterpret_cast<PyTypeObject *>(PyType_FromSpec(&SbkObjectType_Type_spec)); #if PY_VERSION_HEX < 0x03000000 - if (add_tp_new_wrapper(type) < 0) + if (patch_tp_new_wrapper(type) < 0) return nullptr; #endif } @@ -452,6 +456,11 @@ PyObject *SbkObjectTypeTpNew(PyTypeObject *metatype, PyObject *args, PyObject *k auto *newType = reinterpret_cast<SbkObjectType *>(type_new(metatype, args, kwds)); if (!newType) return nullptr; +#if PY_VERSION_HEX < 0x03000000 + // PYSIDE-1051: The newly created metatype needs the PYSIDE-816 wrapper, too. + if (patch_tp_new_wrapper(&newType->type) < 0) + return nullptr; +#endif Shiboken::ObjectType::initPrivateData(newType); SbkObjectTypePrivate *sotp = PepType_SOTP(newType); @@ -842,6 +851,11 @@ introduceWrapperType(PyObject *enclosingObject, Py_TYPE(heaptype) = SbkObjectType_TypeF(); Py_INCREF(Py_TYPE(heaptype)); auto *type = reinterpret_cast<SbkObjectType *>(heaptype); +#if PY_VERSION_HEX < 0x03000000 + // PYSIDE-1051: The newly created type needs the PYSIDE-816 wrapper, too. + if (patch_tp_new_wrapper(&type->type) < 0) + return nullptr; +#endif if (baseType) { if (baseTypes) { for (int i = 0; i < PySequence_Fast_GET_SIZE(baseTypes); ++i) diff --git a/sources/shiboken2/libshiboken/signature.cpp b/sources/shiboken2/libshiboken/signature.cpp index e62f861a2..2f080edad 100644 --- a/sources/shiboken2/libshiboken/signature.cpp +++ b/sources/shiboken2/libshiboken/signature.cpp @@ -187,8 +187,12 @@ _get_class_of_descr(PyObject *ob) } static PyObject * -GetClassOfFunc(PyObject *ob) +GetClassOrModOf(PyObject *ob) { + /* + * Return the type or module of a function or type. + * The purpose is finally to use the name of the object. + */ if (PyType_Check(ob)) { // PySide-928: The type case must do refcounting like the others as well. Py_INCREF(ob); @@ -202,7 +206,7 @@ GetClassOfFunc(PyObject *ob) return _get_class_of_descr(ob); if (Py_TYPE(ob) == &PyWrapperDescr_Type) return _get_class_of_descr(ob); - Py_FatalError("unexpected type in GetClassOfFunc"); + Py_FatalError("unexpected type in GetClassOrModOf"); return nullptr; } @@ -227,7 +231,7 @@ compute_name_key(PyObject *ob) if (PyType_Check(ob)) return GetTypeKey(ob); Shiboken::AutoDecRef func_name(get_funcname(ob)); - Shiboken::AutoDecRef type_key(GetTypeKey(GetClassOfFunc(ob))); + Shiboken::AutoDecRef type_key(GetTypeKey(GetClassOrModOf(ob))); return Py_BuildValue("(OO)", type_key.object(), func_name.object()); } @@ -267,7 +271,7 @@ name_key_to_func(PyObject *ob) PyObject *ret = PyDict_GetItem(pyside_globals->map_dict, name_key); if (ret == nullptr) { // do a lazy initialization - Shiboken::AutoDecRef type_key(GetTypeKey(GetClassOfFunc(ob))); + Shiboken::AutoDecRef type_key(GetTypeKey(GetClassOrModOf(ob))); PyObject *type = PyDict_GetItem(pyside_globals->map_dict, type_key); if (type == nullptr) @@ -364,7 +368,7 @@ GetSignature_Function(PyObject *obfunc, const char *modifier) // make sure that we look into PyCFunction, only... if (Py_TYPE(obfunc) == PepFunction_TypePtr) Py_RETURN_NONE; - Shiboken::AutoDecRef obtype_mod(GetClassOfFunc(obfunc)); + Shiboken::AutoDecRef obtype_mod(GetClassOrModOf(obfunc)); Shiboken::AutoDecRef type_key(GetTypeKey(obtype_mod)); if (type_key.isNull()) Py_RETURN_NONE; @@ -674,10 +678,16 @@ handle_doc(PyObject *ob, PyObject *old_descr) { init_module_1(); init_module_2(); - Shiboken::AutoDecRef ob_type(GetClassOfFunc(ob)); - auto *type = reinterpret_cast<PyTypeObject *>(ob_type.object()); - if (handle_doc_in_progress || strncmp(type->tp_name, "PySide2.", 8) != 0) - return PyObject_CallMethod(old_descr, const_cast<char *>("__get__"), const_cast<char *>("(O)"), ob); + Shiboken::AutoDecRef ob_type_mod(GetClassOrModOf(ob)); + const char *name; + if (PyModule_Check(ob_type_mod)) + name = PyModule_GetName(ob_type_mod); + else + name = reinterpret_cast<PyTypeObject *>(ob_type_mod.object())->tp_name; + if (handle_doc_in_progress || name == nullptr + || strncmp(name, "PySide2.", 8) != 0) + return PyObject_CallMethod(old_descr, const_cast<char *>("__get__"), + const_cast<char *>("(O)"), ob); handle_doc_in_progress++; PyObject *res = PyObject_CallFunction( pyside_globals->make_helptext_func, diff --git a/sources/shiboken2/shiboken_version.py b/sources/shiboken2/shiboken_version.py index e253eb8fb..17a6bb515 100644 --- a/sources/shiboken2/shiboken_version.py +++ b/sources/shiboken2/shiboken_version.py @@ -39,7 +39,7 @@ major_version = "5" minor_version = "12" -patch_version = "4" +patch_version = "5" # For example: "a", "b", "rc" # (which means "alpha", "beta", "release candidate"). diff --git a/sources/shiboken2/shibokenmodule/CMakeLists.txt b/sources/shiboken2/shibokenmodule/CMakeLists.txt index 09731240f..057a995f8 100644 --- a/sources/shiboken2/shibokenmodule/CMakeLists.txt +++ b/sources/shiboken2/shibokenmodule/CMakeLists.txt @@ -61,6 +61,8 @@ configure_file("${CMAKE_CURRENT_SOURCE_DIR}/files.dir/shibokensupport/signature/ "${CMAKE_CURRENT_BINARY_DIR}/files.dir/shibokensupport/signature/lib/__init__.py" COPYONLY) configure_file("${CMAKE_CURRENT_SOURCE_DIR}/files.dir/shibokensupport/signature/lib/enum_sig.py" "${CMAKE_CURRENT_BINARY_DIR}/files.dir/shibokensupport/signature/lib/enum_sig.py" COPYONLY) +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/files.dir/shibokensupport/signature/lib/tool.py" + "${CMAKE_CURRENT_BINARY_DIR}/files.dir/shibokensupport/signature/lib/tool.py" COPYONLY) if (PYTHON_VERSION_MAJOR EQUAL 3) else() configure_file("${CMAKE_CURRENT_SOURCE_DIR}/files.dir/shibokensupport/backport_inspect.py" diff --git a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/layout.py b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/layout.py index c43d6d076..bd827f1ee 100644 --- a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/layout.py +++ b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/layout.py @@ -58,20 +58,7 @@ used literally as strings like "signature", "existence", etc. from textwrap import dedent from shibokensupport.signature import inspect from shibokensupport.signature.mapping import ellipsis - - -class SimpleNamespace(object): - # From types.rst, because the builtin is implemented in Python 3, only. - def __init__(self, **kwargs): - self.__dict__.update(kwargs) - - def __repr__(self): - keys = sorted(self.__dict__) - items = ("{}={!r}".format(k, self.__dict__[k]) for k in keys) - return "{}({})".format(type(self).__name__, ", ".join(items)) - - def __eq__(self, other): - return self.__dict__ == other.__dict__ +from shibokensupport.signature.lib.tool import SimpleNamespace class SignatureLayout(SimpleNamespace): diff --git a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/lib/tool.py b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/lib/tool.py new file mode 100644 index 000000000..b34bfb404 --- /dev/null +++ b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/lib/tool.py @@ -0,0 +1,135 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +from __future__ import print_function, absolute_import + +""" +tool.py + +Some useful stuff, see below. +""" + +from textwrap import dedent + + +class SimpleNamespace(object): + # From types.rst, because the builtin is implemented in Python 3, only. + def __init__(self, **kwargs): + self.__dict__.update(kwargs) + + def __repr__(self): + keys = sorted(self.__dict__) + items = ("{}={!r}".format(k, self.__dict__[k]) for k in keys) + return "{}({})".format(type(self).__name__, ", ".join(items)) + + def __eq__(self, other): + return self.__dict__ == other.__dict__ + +try: + from types import SimpleNamespace +except ImportError: + pass + + +def build_brace_pattern(level, separators=""): + """ + Build a brace pattern upto a given depth + + The brace pattern parses any pattern with round, square, curly, or angle + brackets. Inside those brackets, any characters are allowed. + + The structure is quite simple and is recursively repeated as needed. + When separators are given, the match stops at that separator. + + Reason to use this instead of some Python function: + The resulting regex is _very_ fast! + + A faster replacement would be written in C, but this solution is + sufficient when the nesting level is not too large. + + Because of the recursive nature of the pattern, the size grows by a factor + of 4 at every level, as does the creation time. Up to a level of 6, this + is below 10 ms. + + There are other regex engines available which allow recursive patterns, + avoiding this problem completely. It might be considered to switch to + such an engine if the external module is not a problem. + """ + def escape(str): + return "".join("\\" + c for c in str) + + ro, rc = round = "()" + so, sc = square = "[]" + co, cc = curly = "CD" # we insert "{}", later... + ao, ac = angle = "<>" + qu, bs = '"', "\\" + all = round + square + curly + angle + __ = " " + ro, rc, so, sc, co, cc, ao, ac, separators, qu, bs, all = map( + escape, (ro, rc, so, sc, co, cc, ao, ac, separators, qu, bs, all)) + + no_brace_sep_q = r"[^{all}{separators}{qu}{bs}]".format(**locals()) + no_quote = r"(?: [^{qu}{bs}] | {bs}. )*".format(**locals()) + pattern = dedent(r""" + ( + (?: {__} {no_brace_sep_q} + | {qu} {no_quote} {qu} + | {ro} {replacer} {rc} + | {so} {replacer} {sc} + | {co} {replacer} {cc} + | {ao} {replacer} {ac} + )* + ) + """) + no_braces_q = "[^{all}{qu}{bs}]*".format(**locals()) + repeated = dedent(r""" + {indent} (?: {__} {no_braces_q} + {indent} | {qu} {no_quote} {qu} + {indent} | {ro} {replacer} {rc} + {indent} | {so} {replacer} {sc} + {indent} | {co} {replacer} {cc} + {indent} | {ao} {replacer} {ac} + {indent} )* + """) + for idx in range(level): + pattern = pattern.format(replacer = repeated if idx < level-1 else no_braces_q, + indent = idx * " ", **locals()) + return pattern.replace("C", "{").replace("D", "}") + +# eof diff --git a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/mapping.py b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/mapping.py index 5f92446cf..36b137104 100644 --- a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/mapping.py +++ b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/mapping.py @@ -61,24 +61,13 @@ class ellipsis(object): return "..." ellipsis = ellipsis() -StringList = typing.List[str] -IntList = typing.List[int] Point = typing.Tuple[float, float] -PointList = typing.List[Point] -IntMatrix = typing.List[IntList] Variant = typing.Any ModelIndexList = typing.List[int] QImageCleanupFunction = typing.Callable +StringList = typing.List[str] -# First time installing our own Pair type into typing. -T = TypeVar('T') -S = TypeVar('S') - -class Pair(Generic[T, S]): - __module__ = "typing" - -typing.Pair = Pair - +_S = TypeVar("_S") # Building our own Char type, which is much nicer than # Char = typing.Union[str, int] # how do I model the limitation to 1 char? @@ -174,7 +163,12 @@ class _NotCalled(str): text = self if self.endswith(")") else self + "()" return eval(text, namespace) -USE_PEP563 = sys.version_info[:2] >= (3, 7) +USE_PEP563 = False +# Note: we cannot know if this feature has been imported. +# Otherwise it would be "sys.version_info[:2] >= (3, 7)". +# We *can* eventually inspect sys.modules and look if +# the calling module has this future statement set, +# but should we do that? # Some types are abstract. They just show their name. @@ -183,10 +177,11 @@ class Virtual(_NotCalled): # Other types I simply could not find. class Missing(_NotCalled): - if not USE_PEP563: - # The string must be quoted, because the object does not exist. - def __repr__(self): - return '{}("{}")'.format(type(self).__name__, self) + # The string must be quoted, because the object does not exist. + def __repr__(self): + if USE_PEP563: + return _NotCalled.__repr__(self) + return '{}("{}")'.format(type(self).__name__, self) class Invalid(_NotCalled): @@ -230,7 +225,7 @@ class Reloader(object): self.sys_module_count = len(sys.modules) g = globals() # PYSIDE-1009: Try to recognize unknown modules in errorhandler.py - candidates = list(mod_name for mod_name in sys.modules + candidates = list(mod_name for mod_name in sys.modules.copy() if self.module_valid(sys.modules[mod_name])) for mod_name in candidates: # 'top' is PySide2 when we do 'import PySide.QtCore' @@ -260,20 +255,76 @@ type_map = {} namespace = globals() # our module's __dict__ type_map.update({ + "bool": bool, + "char": Char, + "char*": str, + "char*const": str, + "double": float, + "float": float, + "int": int, + "List": typing.List, + "long": int, + "PyCallable": typing.Callable, + "PyObject": object, + "PySequence": typing.Iterable, # important for numpy + "PyTypeObject": type, + "QChar": Char, + "QHash": typing.Dict, + "qint16": int, + "qint32": int, + "qint64": int, + "qint8": int, + "qintptr": int, "QList": typing.List, - "QVector": typing.List, - "QSet": typing.Set, - "QPair": Pair, + "qlonglong": int, "QMap": typing.Dict, + "QPair": typing.Tuple, + "qptrdiff": int, + "qreal": float, + "QSet": typing.Set, + "QString": str, + "QStringList": StringList, + "quint16": int, + "quint32": int, + "quint32": int, + "quint64": int, + "quint8": int, + "quintptr": int, + "qulonglong": int, + "QVariant": Variant, + "QVector": typing.List, + "real": float, + "short": int, + "signed char": Char, + "signed long": int, + "str": str, + "true": True, + "ULONG_MAX": ulong_max, + "unsigned char": Char, + "unsigned int": int, # should we define an unsigned type? + "unsigned long int": int, # 5.6, RHEL 6.6 + "unsigned long long": int, + "unsigned long": int, + "unsigned short int": int, # 5.6, RHEL 6.6 + "unsigned short": int, + "UnsignedShortType": int, # 5.9 + "void": int, # be more specific? + "WId": WId, + "zero(bytes)": b"", + "zero(Char)": 0, + "zero(float)": 0, + "zero(int)": 0, + "zero(object)": None, + "zero(str)": "", }) # The Shiboken Part def init_Shiboken(): type_map.update({ + "PyType": type, "shiboken2.bool": bool, "size_t": int, - "PyType": type, }) return locals() @@ -288,36 +339,32 @@ def init_minimal(): def init_sample(): import datetime type_map.update({ - "double": float, - "sample.int": int, + "char": Char, "Complex": complex, - "sample.OddBool": bool, - "sample.bool": bool, - "sample.PStr": str, + "double": float, + "Foo.HANDLE": int, + "HANDLE": int, + "Null": None, + "ObjectType.Identifier": Missing("sample.ObjectType.Identifier"), "OddBool": bool, "PStr": str, - "char": Char, + "PyDate": datetime.date, + "sample.bool": bool, "sample.char": Char, - "sample.Point": Point, + "sample.double": float, + "sample.int": int, "sample.ObjectType": object, - "std.string": str, - "HANDLE": int, - "Foo.HANDLE": int, + "sample.OddBool": bool, "sample.Photon.TemplateBase": Missing("sample.Photon.TemplateBase"), - "ObjectType.Identifier": Missing("sample.ObjectType.Identifier"), - "zero(HANDLE)": 0, - "Null": None, - "zero(sample.ObjectType)": None, + "sample.Point": Point, + "sample.PStr": str, + "sample.unsigned char": Char, "std.size_t": int, - 'Str("<unknown>")': "<unknown>", + "std.string": str, + "ZeroIn": 0, 'Str("<unk")': "<unk", + 'Str("<unknown>")': "<unknown>", 'Str("nown>")': "nown>", - "zero(sample.ObjectModel)": None, - "sample.unsigned char": Char, - "sample.double": float, - "zero(sample.bool)": False, - "PyDate": datetime.date, - "ZeroIn": 0, }) return locals() @@ -325,15 +372,20 @@ def init_sample(): def init_other(): import numbers type_map.update({ - "other.Number": numbers.Number, "other.ExtendsNoImplicitConversion": Missing("other.ExtendsNoImplicitConversion"), + "other.Number": numbers.Number, }) return locals() def init_smart(): + # This missing type should be defined in module smart. We cannot set it to Missing() + # because it is a container type. Therefore, we supply a surrogate: + global SharedPtr + class SharedPtr(Generic[_S]): + __module__ = "smart" + smart.SharedPtr = SharedPtr type_map.update({ - "smart.SharedPtr": Missing("smart.SharedPtr"), # bad object "SharedPtr<Obj >" "smart.Smart.Integer2": int, }) return locals() @@ -349,131 +401,88 @@ def init_PySide2_QtCore(): except ImportError: pass type_map.update({ - "str": str, - "int": int, - "QString": str, - "bool": bool, - "PyObject": object, - "void": int, # be more specific? - "char": Char, - "'%'": "%", "' '": " ", - "false": False, - "double": float, + "'%'": "%", "'g'": "g", + "4294967295UL": 4294967295, # 5.6, RHEL 6.6 + "CheckIndexOption.NoOption": Instance( + "PySide2.QtCore.QAbstractItemModel.CheckIndexOptions.NoOption"), # 5.11 + "false": False, + "list of QAbstractAnimation": typing.List[PySide2.QtCore.QAbstractAnimation], + "list of QAbstractState": typing.List[PySide2.QtCore.QAbstractState], "long long": int, - "unsigned int": int, # should we define an unsigned type? - "Q_NULLPTR": None, "long": int, - "float": float, - "short": int, - "unsigned long": int, - "unsigned long long": int, - "unsigned short": int, - "QStringList": StringList, - "QChar": Char, - "signed char": Char, - "QVariant": Variant, - "QVariant.Type": type, # not so sure here... - "QStringRef": str, - "QString()": "", - "QModelIndexList": ModelIndexList, - "unsigned char": Char, - "QJsonObject": typing.Dict[str, PySide2.QtCore.QJsonValue], - "QStringList()": [], - "ULONG_MAX": ulong_max, - "quintptr": int, + "NULL": None, # 5.6, MSVC + "nullptr": None, # 5.9 + "PyByteArray": bytearray, + "PyBytes": bytes, "PyCallable": typing.Callable, - "PyTypeObject": type, + "PyObject": object, "PySequence": typing.Iterable, # important for numpy - "qptrdiff": int, - "true": True, - "Qt.HANDLE": int, # be more explicit with some consts? - "list of QAbstractState": typing.List[PySide2.QtCore.QAbstractState], - "list of QAbstractAnimation": typing.List[PySide2.QtCore.QAbstractAnimation], - "QVariant()": Invalid(Variant), "PySide2.QtCore.bool": bool, - "QHash": typing.Dict, - "PySide2.QtCore.QChar": Char, - "PySide2.QtCore.qreal": float, + "PySide2.QtCore.char": StringList, # A 'char **' is a list of strings. + "PySide2.QtCore.double": float, "PySide2.QtCore.float": float, + "PySide2.QtCore.int": int, + "PySide2.QtCore.int32_t": int, # 5.9 + "PySide2.QtCore.int64_t": int, # 5.9 + "PySide2.QtCore.long long": int, # 5.9, MSVC 15 + "PySide2.QtCore.long": int, + "PySide2.QtCore.QCborStreamReader.StringResult": typing.AnyStr, + "PySide2.QtCore.QChar": Char, "PySide2.QtCore.qint16": int, "PySide2.QtCore.qint32": int, "PySide2.QtCore.qint64": int, "PySide2.QtCore.qint8": int, + "PySide2.QtCore.qreal": float, "PySide2.QtCore.QString": str, "PySide2.QtCore.QStringList": StringList, - "PySide2.QtCore.QVariant": Variant, "PySide2.QtCore.quint16": int, "PySide2.QtCore.quint32": int, "PySide2.QtCore.quint64": int, "PySide2.QtCore.quint8": int, + "PySide2.QtCore.QUrl.ComponentFormattingOptions": + PySide2.QtCore.QUrl.ComponentFormattingOption, # mismatch option/enum, why??? + "PySide2.QtCore.QVariant": Variant, "PySide2.QtCore.short": int, - "PySide2.QtCore.unsigned short": int, "PySide2.QtCore.signed char": Char, "PySide2.QtCore.uchar": Char, + "PySide2.QtCore.uint32_t": int, # 5.9 "PySide2.QtCore.unsigned char": Char, # 5.9 - "PySide2.QtCore.long": int, - "PySide2.QtCore.QUrl.ComponentFormattingOptions": - PySide2.QtCore.QUrl.ComponentFormattingOption, # mismatch option/enum, why??? - "QUrl.FormattingOptions(PrettyDecoded)": Instance( - "QUrl.FormattingOptions(QUrl.PrettyDecoded)"), - # from 5.9 + "PySide2.QtCore.unsigned int": int, # 5.9 Ubuntu + "PySide2.QtCore.unsigned short": int, + "PyTypeObject": type, + "PyUnicode": typing.Text, + "Q_NULLPTR": None, + "QChar": Char, "QDir.Filters(AllEntries | NoDotAndDotDot)": Instance( "QDir.Filters(QDir.AllEntries | QDir.NoDotAndDotDot)"), - "NULL": None, # 5.6, MSVC "QDir.SortFlags(Name | IgnoreCase)": Instance( "QDir.SortFlags(QDir.Name | QDir.IgnoreCase)"), - "PyBytes": bytes, - "PyByteArray": bytearray, - "PyUnicode": typing.Text, - "signed long": int, - "PySide2.QtCore.int": int, - "PySide2.QtCore.char": StringList, # A 'char **' is a list of strings. - "unsigned long int": int, # 5.6, RHEL 6.6 - "unsigned short int": int, # 5.6, RHEL 6.6 - "4294967295UL": 4294967295, # 5.6, RHEL 6.6 - "PySide2.QtCore.int32_t": int, # 5.9 - "PySide2.QtCore.int64_t": int, # 5.9 - "UnsignedShortType": int, # 5.9 - "nullptr": None, # 5.9 - "uint64_t": int, # 5.9 - "PySide2.QtCore.uint32_t": int, # 5.9 - "PySide2.QtCore.unsigned int": int, # 5.9 Ubuntu - "PySide2.QtCore.long long": int, # 5.9, MSVC 15 - "QGenericArgument(nullptr)": ellipsis, # 5.10 - "QModelIndex()": Invalid("PySide2.QtCore.QModelIndex"), # repr is btw. very wrong, fix it?! "QGenericArgument((0))": ellipsis, # 5.6, RHEL 6.6. Is that ok? "QGenericArgument()": ellipsis, "QGenericArgument(0)": ellipsis, "QGenericArgument(NULL)": ellipsis, # 5.6, MSVC + "QGenericArgument(nullptr)": ellipsis, # 5.10 "QGenericArgument(Q_NULLPTR)": ellipsis, - "zero(PySide2.QtCore.QObject)": None, - "zero(PySide2.QtCore.QThread)": None, - "zero(quintptr)": 0, - "zero(str)": "", - "zero(int)": 0, - "zero(PySide2.QtCore.QState)": None, - "zero(PySide2.QtCore.bool)": False, - "zero(PySide2.QtCore.int)": 0, - "zero(void)": None, - "zero(long long)": 0, - "zero(PySide2.QtCore.QAbstractItemModel)": None, - "zero(PySide2.QtCore.QJsonParseError)": None, - "zero(double)": 0.0, - "zero(PySide2.QtCore.qint64)": 0, - "zero(PySide2.QtCore.QTextCodec.ConverterState)": None, - "zero(long long)": 0, - "zero(QImageCleanupFunction)": None, - "zero(unsigned int)": 0, - "zero(PySide2.QtCore.QPoint)": Default("PySide2.QtCore.QPoint"), - "zero(unsigned char)": 0, - "zero(PySide2.QtCore.QEvent.Type)": None, - "CheckIndexOption.NoOption": Instance( - "PySide2.QtCore.QAbstractItemModel.CheckIndexOptions.NoOption"), # 5.11 + "QHash": typing.Dict, + "QJsonObject": typing.Dict[str, PySide2.QtCore.QJsonValue], + "QModelIndex()": Invalid("PySide2.QtCore.QModelIndex"), # repr is btw. very wrong, fix it?! + "QModelIndexList": ModelIndexList, + "qptrdiff": int, + "QString": str, + "QString()": "", + "QStringList": StringList, + "QStringList()": [], + "QStringRef": str, + "Qt.HANDLE": int, # be more explicit with some consts? + "quintptr": int, + "QUrl.FormattingOptions(PrettyDecoded)": Instance( + "QUrl.FormattingOptions(QUrl.PrettyDecoded)"), + "QVariant": Variant, + "QVariant()": Invalid(Variant), + "QVariant.Type": type, # not so sure here... "QVariantMap": typing.Dict[str, Variant], - "PySide2.QtCore.QCborStreamReader.StringResult": typing.AnyStr, - "PySide2.QtCore.double": float, }) try: type_map.update({ @@ -488,29 +497,21 @@ def init_PySide2_QtCore(): def init_PySide2_QtGui(): from PySide2.QtGui import QPageLayout, QPageSize # 5.12 macOS type_map.update({ - "QVector< QTextLayout.FormatRange >()": [], # do we need more structure? - "USHRT_MAX": ushort_max, "0.0f": 0.0, "1.0f": 1.0, - "uint32_t": int, - "uint8_t": int, - "int32_t": int, "GL_COLOR_BUFFER_BIT": GL_COLOR_BUFFER_BIT, "GL_NEAREST": GL_NEAREST, - "WId": WId, + "int32_t": int, + "PySide2.QtCore.uint8_t": int, # macOS 5.9 + "PySide2.QtGui.QGenericMatrix": Missing("PySide2.QtGui.QGenericMatrix"), "PySide2.QtGui.QPlatformSurface": int, # a handle "QList< QTouchEvent.TouchPoint >()": [], # XXX improve? "QPixmap()": Default("PySide2.QtGui.QPixmap"), # can't create without qApp - "PySide2.QtCore.uint8_t": int, # macOS 5.9 - "zero(uint32_t)": 0, - "zero(PySide2.QtGui.QWindow)": None, - "zero(PySide2.QtGui.QOpenGLContext)": None, - "zero(PySide2.QtGui.QRegion)": None, - "zero(PySide2.QtGui.QPaintDevice)": None, - "zero(PySide2.QtGui.QTextLayout.FormatRange)": None, - "zero(PySide2.QtGui.QTouchDevice)": None, - "zero(PySide2.QtGui.QScreen)": None, - "PySide2.QtGui.QGenericMatrix": Missing("PySide2.QtGui.QGenericMatrix"), + "QVector< QTextLayout.FormatRange >()": [], # do we need more structure? + "uint32_t": int, + "uint8_t": int, + "USHRT_MAX": ushort_max, + "WId": WId, }) return locals() @@ -521,28 +522,15 @@ def init_PySide2_QtWidgets(): type_map.update({ "QMessageBox.StandardButtons(Yes | No)": Instance( "QMessageBox.StandardButtons(QMessageBox.Yes | QMessageBox.No)"), + "QVector< int >()": [], "QWidget.RenderFlags(DrawWindowBackground | DrawChildren)": Instance( "QWidget.RenderFlags(QWidget.DrawWindowBackground | QWidget.DrawChildren)"), + "SH_Default": QStyleHintReturn.SH_Default, + "SO_Complex": QStyleOptionComplex.SO_Complex, + "SO_Default": QStyleOption.SO_Default, "static_cast<Qt.MatchFlags>(Qt.MatchExactly|Qt.MatchCaseSensitive)": Instance( "Qt.MatchFlags(Qt.MatchExactly | Qt.MatchCaseSensitive)"), - "QVector< int >()": [], - "WId": WId, - # from 5.9 "Type": PySide2.QtWidgets.QListWidgetItem.Type, - "SO_Default": QStyleOption.SO_Default, - "SH_Default": QStyleHintReturn.SH_Default, - "SO_Complex": QStyleOptionComplex.SO_Complex, - "zero(PySide2.QtWidgets.QWidget)": None, - "zero(PySide2.QtWidgets.QGraphicsItem)": None, - "zero(PySide2.QtCore.QEvent)": None, - "zero(PySide2.QtWidgets.QStyleOption)": None, - "zero(PySide2.QtWidgets.QStyleHintReturn)": None, - "zero(PySide2.QtWidgets.QGraphicsLayoutItem)": None, - "zero(PySide2.QtWidgets.QListWidget)": None, - "zero(PySide2.QtGui.QKeySequence)": None, - "zero(PySide2.QtWidgets.QAction)": None, - "zero(PySide2.QtWidgets.QUndoCommand)": None, - "zero(WId)": 0, }) return locals() @@ -559,9 +547,6 @@ def init_PySide2_QtSql(): def init_PySide2_QtNetwork(): type_map.update({ "QMultiMap": MultiMap, - "zero(unsigned short)": 0, - "zero(PySide2.QtCore.QIODevice)": None, - "zero(QList)": [], }) return locals() @@ -569,8 +554,8 @@ def init_PySide2_QtNetwork(): def init_PySide2_QtXmlPatterns(): from PySide2.QtXmlPatterns import QXmlName type_map.update({ + "QXmlName.NamespaceCode": Missing("PySide2.QtXmlPatterns.QXmlName.NamespaceCode"), "QXmlName.PrefixCode": Missing("PySide2.QtXmlPatterns.QXmlName.PrefixCode"), - "QXmlName.NamespaceCode": Missing("PySide2.QtXmlPatterns.QXmlName.NamespaceCode") }) return locals() @@ -588,39 +573,31 @@ def init_PySide2_QtMultimedia(): def init_PySide2_QtOpenGL(): type_map.update({ - "GLuint": int, + "GLbitfield": int, "GLenum": int, + "GLfloat": float, # 5.6, MSVC 15 "GLint": int, - "GLbitfield": int, + "GLuint": int, "PySide2.QtOpenGL.GLint": int, "PySide2.QtOpenGL.GLuint": int, - "GLfloat": float, # 5.6, MSVC 15 - "zero(PySide2.QtOpenGL.QGLContext)": None, - "zero(GLenum)": 0, - "zero(PySide2.QtOpenGL.QGLWidget)": None, }) return locals() def init_PySide2_QtQml(): type_map.update({ - "QJSValueList()": [], "PySide2.QtQml.bool volatile": bool, - # from 5.9 + "QJSValueList()": [], "QVariantHash()": typing.Dict[str, Variant], # XXX sorted? - "zero(PySide2.QtQml.QQmlContext)": None, - "zero(PySide2.QtQml.QQmlEngine)": None, }) return locals() def init_PySide2_QtQuick(): type_map.update({ - "PySide2.QtQuick.QSharedPointer": int, "PySide2.QtCore.uint": int, + "PySide2.QtQuick.QSharedPointer": int, "T": int, - "zero(PySide2.QtQuick.QQuickItem)": None, - "zero(GLuint)": 0, }) return locals() @@ -638,13 +615,6 @@ def init_PySide2_QtTest(): }) return locals() -# from 5.9 -def init_PySide2_QtWebEngineWidgets(): - type_map.update({ - "zero(PySide2.QtWebEngineWidgets.QWebEnginePage.FindFlags)": 0, - }) - return locals() - # from 5.6, MSVC def init_PySide2_QtWinExtras(): type_map.update({ diff --git a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py index 72ca35757..6109bceee 100644 --- a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py +++ b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/signature/parser.py @@ -47,6 +47,8 @@ import keyword import functools from shibokensupport.signature.mapping import (type_map, update_mapping, namespace, typing, _NotCalled) +from shibokensupport.signature.lib.tool import (SimpleNamespace, + build_brace_pattern) _DEBUG = False LIST_KEYWORDS = False @@ -78,6 +80,23 @@ def dprint(*args, **kw): pprint.pprint(arg) sys.stdout.flush() + +_cache = {} + +def _parse_arglist(argstr): + # The following is a split re. The string is broken into pieces which are + # between the recognized strings. Because the re has groups, both the + # strings and the separators are returned, where the strings are not + # interesting at all: They are just the commata. + key = "_parse_arglist" + if key not in _cache: + regex = build_brace_pattern(level=3, separators=",") + _cache[key] = re.compile(regex, flags=re.VERBOSE) + split = _cache[key].split + # Note: this list is interspersed with "," and surrounded by "" + return [x.strip() for x in split(argstr) if x.strip() not in ("", ",")] + + def _parse_line(line): line_re = r""" ((?P<multi> ([0-9]+)) : )? # the optional multi-index @@ -86,38 +105,9 @@ def _parse_line(line): ( -> (?P<returntype> .*) )? # the optional return type $ """ - ret = re.match(line_re, line, re.VERBOSE).groupdict() - arglist = ret["arglist"] - # The following is a split re. The string is broken into pieces which are - # between the recognized strings. Because the re has groups, both the - # strings and the delimiters are returned, where the strings are not - # interesting at all: They are just the commata. - # Note that it is necessary to put the characters with special handling in - # the first group (comma, brace, angle bracket). - # Then they are not recognized there, and we can handle them differently - # in the following expressions. - arglist = list(x.strip() for x in re.split(r""" - ( - (?: # inner group is not capturing - [^,()<>] # no commas or braces or angle brackets - | - \( - (?: - [^()]* # or one brace pair - | - \( - [^()]* # or doubls nested pair - \) - )* - \) - | - < # or one angle bracket pair - [^<>]* - > - )+ # longest possible span - ) # this list is interspersed with "," and surrounded by "" - """, arglist, flags=re.VERBOSE) - if x.strip() not in ("", ",")) + ret = SimpleNamespace(**re.match(line_re, line, re.VERBOSE).groupdict()) + argstr = ret.arglist + arglist = _parse_arglist(argstr) args = [] for arg in arglist: name, ann = arg.split(":") @@ -131,15 +121,16 @@ def _parse_line(line): else: tup = name, ann args.append(tup) - ret["arglist"] = args - multi = ret["multi"] + ret.arglist = args + multi = ret.multi if multi is not None: - ret["multi"] = int(multi) - funcname = ret["funcname"] + ret.multi = int(multi) + funcname = ret.funcname parts = funcname.split(".") if parts[-1] in keyword.kwlist: - ret["funcname"] = funcname + "_" - return ret + ret.funcname = funcname + "_" + return vars(ret) + def make_good_value(thing, valtype): try: @@ -153,6 +144,7 @@ def make_good_value(thing, valtype): except Exception: pass + def try_to_guess(thing, valtype): if "." not in thing and "(" not in thing: text = "{}.{}".format(valtype, thing) @@ -172,9 +164,13 @@ def try_to_guess(thing, valtype): return ret return None + def _resolve_value(thing, valtype, line): if thing in ("0", "None") and valtype: - thing = "zero({})".format(valtype) + if valtype.startswith("PySide2."): + return None + name = type_map[valtype].__name__ + thing = "zero({})".format(name) if thing in type_map: return type_map[thing] res = make_good_value(thing, valtype) @@ -192,40 +188,88 @@ def _resolve_value(thing, valtype, line): """.format(thing, line), RuntimeWarning) return thing + def _resolve_arraytype(thing, line): - thing = thing[:-2] - if thing.endswith("[]"): + search = re.search(r"\[(\d*)\]$", thing) + thing = thing[:search.start()] + if thing.endswith("]"): thing = _resolve_arraytype(thing, line) - # this mapping is in shiboken - thing = "QList[" + thing + "]" + if search.group(1): + # concrete array, use a tuple + nelem = int(search.group(1)) + thing = ", ".join([thing] * nelem) + thing = "Tuple[" + thing + "]" + else: + thing = "QList[" + thing + "]" return thing + def to_string(thing): if isinstance(thing, str): return thing if hasattr(thing, "__name__"): - dot = "." in str(type(thing)) + dot = "." in str(thing) return thing.__module__ + "." + thing.__name__ if dot else thing.__name__ # Note: This captures things from the typing module: return str(thing) -def _resolve_type(thing, line): - if thing.endswith("[]"): - thing = _resolve_arraytype(thing, line) + +matrix_pattern = "PySide2.QtGui.QGenericMatrix" + +# The matrix patch is borrowed from the future (extracted). +# It will work when the parser recognizes matrices. +def handle_matrix(arg): + n, m, typstr = tuple(map(lambda x:x.strip(), arg.split(","))) + assert typstr == "float" + result = "PySide2.QtGui.QMatrix{n}x{m}".format(**locals()) + return eval(result, namespace) + + +debugging_aid = """ +from inspect import currentframe + +def lno(level): + lineno = currentframe().f_back.f_lineno + spaces = level * " " + return "{lineno}{spaces}".format(**locals()) +""" + + +def _resolve_type(thing, line, level): + # Capture total replacements, first. Happens in + # "PySide2.QtCore.QCborStreamReader.StringResult[PySide2.QtCore.QByteArray]" + if thing in type_map: + return type_map[thing] + # Now the nested structures are handled. if "[" in thing: + # handle primitive arrays + if re.search(r"\[\d*\]$", thing): + thing = _resolve_arraytype(thing, line) # Handle a container return type. (see PYSIDE-921 in cppgenerator.cpp) contr, thing = re.match(r"(.*?)\[(.*?)\]$", thing).groups() - contr = to_string(_resolve_type(contr, line)) - thing = to_string(_resolve_type(thing, line)) + # Special case: Handle the generic matrices. + if contr == matrix_pattern: + return handle_matrix(thing) + contr = _resolve_type(contr, line, level+1) + if isinstance(contr, _NotCalled): + raise SystemError("Container types must exist:", repr(contr)) + contr = to_string(contr) + pieces = [] + for part in _parse_arglist(thing): + part = _resolve_type(part, line, level+1) + if isinstance(part, _NotCalled): + # fix the tag (i.e. "Missing") by repr + part = repr(part) + pieces.append(to_string(part)) + thing = ", ".join(pieces) result = "{contr}[{thing}]".format(**locals()) - if not isinstance(thing, _NotCalled): - result = eval(result, namespace) - return result + return eval(result, namespace) return _resolve_value(thing, None, line) + def calculate_props(line): - res = _parse_line(line) - arglist = res["arglist"] + parsed = SimpleNamespace(**_parse_line(line.strip())) + arglist = parsed.arglist annotations = {} _defaults = [] for idx, tup in enumerate(arglist): @@ -236,27 +280,33 @@ def calculate_props(line): ann = 'NULL' # maps to None tup = name, ann arglist[idx] = tup - annotations[name] = _resolve_type(ann, line) + annotations[name] = _resolve_type(ann, line, 0) if len(tup) == 3: default = _resolve_value(tup[2], ann, line) _defaults.append(default) defaults = tuple(_defaults) - returntype = res["returntype"] + returntype = parsed.returntype if returntype is not None: - annotations["return"] = _resolve_type(returntype, line) - props = {} - props["defaults"] = defaults - props["kwdefaults"] = {} - props["annotations"] = annotations - props["varnames"] = varnames = tuple(tup[0] for tup in arglist) - funcname = res["funcname"] - props["fullname"] = funcname + annotations["return"] = _resolve_type(returntype, line, 0) + props = SimpleNamespace() + props.defaults = defaults + props.kwdefaults = {} + props.annotations = annotations + props.varnames = varnames = tuple(tup[0] for tup in arglist) + funcname = parsed.funcname + props.fullname = funcname shortname = funcname[funcname.rindex(".")+1:] - props["name"] = shortname - props["multi"] = res["multi"] - return props + props.name = shortname + props.multi = parsed.multi + return vars(props) + def fixup_multilines(lines): + """ + Multilines can collapse when certain distinctions between C++ types + vanish after mapping to Python. + This function fixes this by re-computing multiline-ness. + """ res = [] multi_lines = [] for line in lines: @@ -280,6 +330,7 @@ def fixup_multilines(lines): res.append(line) return res + def pyside_type_init(type_key, sig_strings): dprint() dprint("Initialization of type key '{}'".format(type_key)) diff --git a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/typing27.py b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/typing27.py index dba8f8c77..786a84ecb 100644 --- a/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/typing27.py +++ b/sources/shiboken2/shibokenmodule/files.dir/shibokensupport/typing27.py @@ -1996,6 +1996,21 @@ class DefaultDict(collections.defaultdict, MutableMapping[KT, VT]): return _generic_new(collections.defaultdict, cls, *args, **kwds) +############################ +# Insertion by CT 2019-02-21 +# +class OrderedDict(collections.OrderedDict, MutableMapping[KT, VT]): + __slots__ = () + __extra__ = collections.OrderedDict + + def __new__(cls, *args, **kwds): + if cls._gorg is OrderedDict: + return collections.OrderedDict(*args, **kwds) + return _generic_new(collections.OrderedDict, cls, *args, **kwds) +# +############################ + + class Counter(collections.Counter, Dict[T, int]): __slots__ = () __extra__ = collections.Counter diff --git a/sources/shiboken2/tests/minimalbinding/brace_pattern_test.py b/sources/shiboken2/tests/minimalbinding/brace_pattern_test.py new file mode 100644 index 000000000..89df998e8 --- /dev/null +++ b/sources/shiboken2/tests/minimalbinding/brace_pattern_test.py @@ -0,0 +1,121 @@ +############################################################################# +## +## Copyright (C) 2019 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of Qt for Python. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +from __future__ import absolute_import, print_function + +import re +import sys +import os + +import shiboken2 +type.__signature__ # trigger bootstrap + +from shibokensupport.signature.lib.tool import build_brace_pattern +import unittest + +""" +This test tests the brace pattern from signature.lib.tool +against a slower reference implementation. +The pattern is crucial, because it is used heavily in signature.parser . +""" + +# A slow reference parser for braces and strings +def check(s): + open, close = "[{(<", "]})>" + escape, quote = "\\", '"' + instring = blind = False + stack = [] + for c in s: + if instring: + if blind: + blind = False + elif c == escape: + blind = True + elif c == quote: + instring = False + stack.pop() + continue + if c in open: + stack.append(c) + elif c in close: + pos = close.index(c) + if ((len(stack) > 0) and + (open[pos] == stack[len(stack)-1])): + stack.pop() + else: + return False + elif c == escape: + return False + elif c == quote: + instring = True + stack.append(c) + return len(stack) == 0 + + +class TestBracePattern(unittest.TestCase): + tests = [ + (r'{[]{()}}', True), + (r'[{}{})(]', False), + (r'[{}{} ")(" ]', True), + (r'[{}{} ")(\" ]', False), + (r'[{}{} ")(\" ]"]', True), + (r'a < b ( c [ d { "} \"\"}" } ] ) >', True), + (r'a < b ( c [ d { } ] ) >', True), + (r'a < b ( c [ d { "huh" } ] ) >', True), + (r'a < b ( c [ d { "huh\" \" \\\"" } ] ) >', True), + ] + + def test_checkfunc(self): + for test, result in self.tests: + if result: + self.assertTrue(check(test)) + else: + self.assertFalse(check(test)) + + def test_the_brace_pattern(self): + func = re.compile(build_brace_pattern(5) + "$", re.VERBOSE).match + for test, result in self.tests: + if result: + self.assertTrue(func(test)) + else: + self.assertFalse(func(test)) + + +if __name__ == '__main__': + unittest.main() diff --git a/sources/shiboken2/tests/samplebinding/objecttype_test.py b/sources/shiboken2/tests/samplebinding/objecttype_test.py index bda14c69c..f1a06c2b1 100644 --- a/sources/shiboken2/tests/samplebinding/objecttype_test.py +++ b/sources/shiboken2/tests/samplebinding/objecttype_test.py @@ -118,5 +118,13 @@ class ObjectTypeTest(unittest.TestCase): self.assertLess(abs(before - after), 5) + def testInvalidProperty(self): + o = ObjectType() + try: + o.typo + self.assertFail() + except AttributeError as error: + self.assertEqual(error.args[0], "'sample.ObjectType' object has no attribute 'typo'") + if __name__ == '__main__': unittest.main() diff --git a/sources/shiboken2/tests/smartbinding/smart_pointer_test.py b/sources/shiboken2/tests/smartbinding/smart_pointer_test.py index e07856e61..e1883c7cc 100644 --- a/sources/shiboken2/tests/smartbinding/smart_pointer_test.py +++ b/sources/shiboken2/tests/smartbinding/smart_pointer_test.py @@ -175,5 +175,17 @@ class SmartPointerTests(unittest.TestCase): self.assertEqual(len(ptrToObjList), 0) self.assertEqual(objCount(), 1) + def testInvalidParameter(self): + # Create Obj. + o = Obj() + # Create a shared pointer to an Obj together with an Obj. + ptrToObj = o.giveSharedPtrToObj() + try: + ptrToObj.typo + self.assertFail() + except AttributeError as error: + self.assertEqual(error.args[0], "'smart.SharedPtr_Obj' object has no attribute 'typo'") + + if __name__ == '__main__': unittest.main() |