aboutsummaryrefslogtreecommitdiffstats
path: root/testing/wheel_tester.py
diff options
context:
space:
mode:
Diffstat (limited to 'testing/wheel_tester.py')
-rw-r--r--testing/wheel_tester.py413
1 files changed, 239 insertions, 174 deletions
diff --git a/testing/wheel_tester.py b/testing/wheel_tester.py
index 180526b33..b36ee55a4 100644
--- a/testing/wheel_tester.py
+++ b/testing/wheel_tester.py
@@ -1,41 +1,5 @@
-#############################################################################
-##
-## 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$
-##
-#############################################################################
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
"""
This script is used by Coin (coin_test_instructions.py specifically) to
@@ -51,9 +15,16 @@ looked up in your PATH.
Make sure that some generated wheels already exist in the dist/
directory (e.g. setup.py bdist_wheel was already executed).
"""
-from __future__ import print_function, absolute_import
-import os, sys
+import os
+import platform
+import shutil
+import sys
+import tempfile
+import logging
+from argparse import ArgumentParser, RawTextHelpFormatter
+from pathlib import Path
+from configparser import ConfigParser
try:
this_file = __file__
@@ -61,59 +32,47 @@ except NameError:
this_file = sys.argv[0]
this_file = os.path.abspath(this_file)
this_dir = os.path.dirname(this_file)
-setup_script_dir = os.path.abspath(os.path.join(this_dir, '..'))
+setup_script_dir = os.path.abspath(os.path.join(this_dir, ".."))
sys.path.append(setup_script_dir)
-from build_scripts.options import OPTION_QMAKE
-from build_scripts.options import OPTION_CMAKE
-
-from build_scripts.utils import find_files_using_glob
-from build_scripts.utils import find_glob_in_path
-from build_scripts.utils import run_process, run_process_output
-from build_scripts.utils import rmtree
-import distutils.log as log
-import platform
-
-log.set_verbosity(1)
+from build_scripts.utils import (find_files_using_glob, find_glob_in_path, # noqa: E402
+ remove_tree, run_process, run_process_output)
+from build_scripts.log import log # noqa: E402
+log.setLevel(logging.DEBUG)
-def find_executable_qmake():
- return find_executable('qmake', OPTION_QMAKE)
-
-
-def find_executable_cmake():
- return find_executable('cmake', OPTION_CMAKE)
+NEW_WHEELS = False
def find_executable(executable, command_line_value):
value = command_line_value
- option_str = '--{}'.format(executable)
+ option_str = f"--{executable}"
if value:
- log.info("{} option given: {}".format(option_str, value))
+ log.info(f"{option_str} option given: {value}")
if not os.path.exists(value):
- raise RuntimeError("No executable exists at: {}".format(value))
+ raise RuntimeError(f"No executable exists at: {value}")
else:
- log.info("No {} option given, trying to find {} in PATH.".format(option_str, executable))
+ log.info(f"No {option_str} option given, trying to find {executable} in PATH.")
paths = find_glob_in_path(executable)
- log.info("{} executables found in PATH: {}".format(executable, paths))
+ log.info(f"{executable} executables found in PATH: {paths}")
if not paths:
raise RuntimeError(
- "No {} option was specified and no {} was found "
- "in PATH.".format(option_str, executable))
+ f"No {option_str} option was specified and no {executable} was " "found in PATH."
+ )
else:
value = paths[0]
- log.info("Using {} found in PATH: {}".format(executable, value))
+ log.info(f"Using {executable} found in PATH: {value}")
log.info("")
return value
-QMAKE_PATH = find_executable_qmake()
-CMAKE_PATH = find_executable_cmake()
+QMAKE_PATH = None
+CMAKE_PATH = None
-def get_wheels_dir():
- return os.path.join(setup_script_dir, "dist")
+def get_wheels_dir(dir_name):
+ return os.path.join(setup_script_dir, dir_name)
def get_examples_dir():
@@ -121,7 +80,13 @@ def get_examples_dir():
def package_prefix_names():
- return ["shiboken2", "shiboken2_generator", "PySide2"]
+ # Note: shiboken6_generator is not needed for compile_using_nuitka,
+ # but building modules with cmake needs it.
+ if NEW_WHEELS:
+ return ["shiboken6", "shiboken6_generator", "PySide6_Essentials", "PySide6_Addons",
+ "PySide6"]
+ else:
+ return ["shiboken6", "shiboken6_generator", "PySide6"]
def clean_egg_info():
@@ -133,16 +98,16 @@ def clean_egg_info():
# safe to do so.
paths = find_files_using_glob(setup_script_dir, "*.egg-info")
for p in paths:
- log.info("Removing {}".format(p))
- rmtree(p)
+ log.info(f"Removing {p}")
+ remove_tree(p)
def install_wheel(wheel_path):
- log.info("Installing wheel: {}".format(wheel_path))
+ log.info(f"Installing wheel: {wheel_path}")
exit_code = run_process([sys.executable, "-m", "pip", "install", wheel_path])
log.info("")
if exit_code:
- raise RuntimeError("Error while installing wheel {}".format(wheel_path))
+ raise RuntimeError(f"Error while installing wheel {wheel_path}")
def try_install_wheels(wheels_dir, py_version):
@@ -151,113 +116,162 @@ def try_install_wheels(wheels_dir, py_version):
all_wheels = find_files_using_glob(wheels_dir, all_wheels_pattern)
if len(all_wheels) > 1:
- log.info("Found the following wheels in {}: ".format(wheels_dir))
+ log.info(f"Found the following wheels in {wheels_dir}: ")
for wheel in all_wheels:
log.info(wheel)
else:
- log.info("No wheels found in {}".format(wheels_dir))
+ log.info(f"No wheels found in {wheels_dir}")
log.info("")
for p in package_prefix_names():
- pattern = "{}-*cp{}*.whl".format(p, py_version)
+ log.info(f"Trying to install {p}:")
+ pattern = f"{p}-*cp{int(float(py_version))}*.whl"
files = find_files_using_glob(wheels_dir, pattern)
if files and len(files) == 1:
wheel_path = files[0]
install_wheel(wheel_path)
elif len(files) > 1:
- raise RuntimeError("More than one wheel found for specific package and version.")
+ raise RuntimeError(f"More than one wheel found for specific {p} version.")
else:
- raise RuntimeError("No wheels compatible with Python {} found "
- "for testing.".format(py_version))
-
-
-def is_unix():
- if sys.platform.startswith("linux") or sys.platform == "darwin":
- return True
- return False
+ raise RuntimeError(
+ f"No {p} wheels compatible with Python {py_version} found " f"for testing."
+ )
def generate_build_cmake():
- args = [CMAKE_PATH]
- if is_unix():
- args.extend(["-G", "Unix Makefiles"])
- else:
- args.extend(["-G", "NMake Makefiles"])
- args.append("-DCMAKE_BUILD_TYPE=Release")
- args.append("-Dpython_interpreter={}".format(sys.executable))
-
- # Specify prefix path so find_package(Qt5) works.
- qmake_dir = os.path.abspath(os.path.join(os.path.dirname(QMAKE_PATH), ".."))
- args.append("-DCMAKE_PREFIX_PATH={}".format(qmake_dir))
-
- args.append("..")
-
+ # Specify prefix path so find_package(Qt6) works.
+ qmake_dir = Path(QMAKE_PATH).resolve().parent.parent
+ args = [CMAKE_PATH, "-G", "Ninja", "-DCMAKE_BUILD_TYPE=Release",
+ f"-Dpython_interpreter={sys.executable}",
+ f"-DCMAKE_PREFIX_PATH={qmake_dir}",
+ ".."]
exit_code = run_process(args)
if exit_code:
raise RuntimeError("Failure while running cmake.")
log.info("")
-def generate_build_qmake():
- exit_code = run_process([QMAKE_PATH, "..", "python_interpreter={}".format(sys.executable)])
- if exit_code:
- raise RuntimeError("Failure while running qmake.")
- log.info("")
-
-
def raise_error_pyinstaller(msg):
print()
- print("PYINST: {msg}".format(**locals()))
- print("PYINST: sys.version = {}".format(sys.version.splitlines()[0]))
- print("PYINST: platform.platform() = {}".format(platform.platform()))
+ print(f"PYINST: {msg}")
+ print(f"PYINST: sys.version = {sys.version.splitlines()[0]}")
+ print(f"PYINST: platform.platform() = {platform.platform()}")
print("PYINST: See the error message above.")
print()
for line in run_process_output([sys.executable, "-m", "pip", "list"]):
- print("PyInstaller pip list: ", line)
+ print(f"PyInstaller pip list: {line}")
print()
- raise(RuntimeError(msg))
+ raise (RuntimeError(msg))
def compile_using_pyinstaller():
- src_path = os.path.join("..", "hello.py")
+ _ = os.path.join("..", "hello.py")
spec_path = os.path.join("..", "hello_app.spec")
exit_code = run_process([sys.executable, "-m", "PyInstaller", spec_path])
- # to create the spec file, this setting was used:
- #"--name=hello_app", "--console", "--log-level=DEBUG", src_path])
- # By using a spec file, we avoid all the probing that might disturb certain
- # platforms and also save some analysis time.
+ # to create the spec file, this setting was used:
+ # "--name=hello_app", "--console", "--log-level=DEBUG", src_path])
+ # By using a spec file, we avoid all the probing that might disturb certain
+ # platforms and also save some analysis time.
if exit_code:
# 2019-04-28 Raising on error is again enabled
raise_error_pyinstaller("Failure while compiling script using PyInstaller.")
log.info("")
-def run_make():
- args = []
- if is_unix():
- executable = "make"
+def test_nuitka(example):
+ testprog = "Nuitka"
+ name = os.path.splitext(os.path.basename(example))[0]
+ print(f"Running {testprog} test of {name}")
+ current_dir = os.getcwd()
+ result = False
+ tmpdirname = tempfile.mkdtemp()
+ try:
+ os.chdir(tmpdirname)
+ cmd = [sys.executable, "-m", "nuitka", "--run", example] # , "--standalone"]
+ _ = run_process(cmd)
+ result = True
+ except RuntimeError as e:
+ print(str(e))
+ finally:
+ os.chdir(current_dir)
+ print(f"Executable is in {tmpdirname}")
+ return result
+
+
+def run_nuitka_test(example):
+ if test_nuitka(example):
+ log.info("")
else:
- executable = "nmake"
- args.append(executable)
-
+ raise RuntimeError(f"Failure running {example} with Nuitka.")
+
+
+def _run_deploy_test(example, tmpdirname):
+ """Helper for running deployment and example."""
+ main_file = None
+ for py_file in example.glob("*.py"):
+ shutil.copy(py_file, tmpdirname)
+ if not main_file or py_file.name == "main.py":
+ main_file = py_file
+ deploy_tool = Path(sys.executable).parent / "pyside6-deploy"
+ cmd = [os.fspath(deploy_tool), "-f", main_file.name, "--init"]
+ if run_process(cmd) != 0:
+ raise RuntimeError("Error creating pysidedeploy.spec")
+
+ config_file = Path(tmpdirname) / "pysidedeploy.spec"
+ parser = ConfigParser(comment_prefixes="/", allow_no_value=True)
+ parser.read(config_file)
+ parser.set("nuitka", "extra_args", "--verbose --assume-yes-for-downloads")
+ with open(config_file, "w+") as config_file_writer:
+ parser.write(config_file_writer, space_around_delimiters=True)
+
+ cmd = [os.fspath(deploy_tool), "-f", "-c", os.fspath(config_file)]
+ if run_process(cmd) != 0:
+ raise RuntimeError("Error deploying")
+
+ suffix = "exe" if sys.platform == "win32" else "bin"
+
+ if sys.platform != "darwin":
+ binary = f"{tmpdirname}/{main_file.stem}.{suffix}"
+ else:
+ binary = f"{tmpdirname}/pyside_app_demo.app/Contents/MacOS/{main_file.stem}"
+
+ if run_process([binary]) != 0:
+ raise RuntimeError("Error running the deployed example")
+ return True
+
+
+def run_deploy_test(example):
+ """Test pyside6-deploy."""
+ log.info(f"Running deploy test of {example}")
+ current_dir = Path.cwd()
+ result = False
+ with tempfile.TemporaryDirectory() as tmpdirname:
+ try:
+ os.chdir(tmpdirname)
+ result = _run_deploy_test(example, tmpdirname)
+ except RuntimeError as e:
+ log.error(str(e))
+ raise e
+ finally:
+ os.chdir(os.fspath(current_dir))
+ state = "succeeded" if result else "failed"
+ log.info(f"Deploy test {state}")
+ return result
+
+
+def run_ninja():
+ args = ["ninja"]
exit_code = run_process(args)
if exit_code:
- raise RuntimeError("Failure while running {}.".format(executable))
+ raise RuntimeError(f"Failure while running {' '.join(args)}.")
log.info("")
-def run_make_install():
- args = []
- if is_unix():
- executable = "make"
- else:
- executable = "nmake"
- args.append(executable)
- args.append("install")
-
+def run_ninja_install():
+ args = ["ninja", "install"]
exit_code = run_process(args)
if exit_code:
- raise RuntimeError("Failed while running {} install.".format(executable))
+ raise RuntimeError(f"Failed while running {' '.join(args)} install.")
log.info("")
@@ -265,28 +279,28 @@ def run_compiled_script(binary_path):
args = [binary_path]
exit_code = run_process(args)
if exit_code:
- raise_error_pyinstaller("Failure while executing compiled script: {}".format(binary_path))
+ raise_error_pyinstaller(f"Failure while executing compiled script: {binary_path}")
log.info("")
-def execute_script(script_path):
- args = [sys.executable, script_path]
+def execute_script(script_path, *extra):
+ args = list(map(str, (sys.executable, script_path) + extra))
exit_code = run_process(args)
if exit_code:
- raise RuntimeError("Failure while executing script: {}".format(script_path))
+ raise RuntimeError(f"Failure while executing script: {script_path}")
log.info("")
def prepare_build_folder(src_path, build_folder_name):
build_path = os.path.join(src_path, build_folder_name)
- # The script can be called for both Python 2 and Python 3 wheels, so
+ # The script can be called for Python 3 wheels, so
# preparing a build folder should clean any previous existing build.
if os.path.exists(build_path):
- log.info("Removing {}".format(build_path))
- rmtree(build_path)
+ log.info(f"Removing {build_path}")
+ remove_tree(build_path)
- log.info("Creating {}".format(build_path))
+ log.info(f"Creating {build_path}")
os.makedirs(build_path)
os.chdir(build_path)
@@ -294,51 +308,102 @@ def prepare_build_folder(src_path, build_folder_name):
def try_build_examples():
examples_dir = get_examples_dir()
- # This script should better go to the last place, here.
- # But because it is most likely to break, we put it here for now.
- log.info("Attempting to build hello.py using PyInstaller.")
- # PyInstaller is loaded by coin_build_instructions.py, but not when
- # testing directly this script.
- src_path = os.path.join(examples_dir, "installer_test")
- prepare_build_folder(src_path, "pyinstaller")
-
- compile_using_pyinstaller()
- run_compiled_script(os.path.join(src_path,
- "pyinstaller", "dist", "hello_app", "hello_app"))
+ # Disabled PyInstaller until it supports PySide 6
+ if False:
+ # But because it is most likely to break, we put it here for now.
+ log.info("Attempting to build hello.py using PyInstaller.")
+ # PyInstaller is loaded by coin_build_instructions.py, but not when
+ # testing directly this script.
+ src_path = os.path.join(examples_dir, "installer_test")
+ prepare_build_folder(src_path, "pyinstaller")
+ compile_using_pyinstaller()
+ run_compiled_script(os.path.join(src_path, "pyinstaller", "dist", "hello_app", "hello_app"))
+
+ log.info("Attempting to build hello.py using Nuitka.")
+ src_path = Path(examples_dir) / "installer_test"
+
+ # disable for windows as it Nuitka --onefile deployment in Windows
+ # requires DependencyWalker. Download and installing will slow down
+ # Coin
+ if sys.platform != "win32":
+ run_deploy_test(src_path)
+
+ if False: # pre 6.4.1, kept for reference
+ # Nuitka is loaded by coin_build_instructions.py, but not when
+ # testing directly this script.
+ run_nuitka_test(os.fspath(src_path / "hello.py"))
log.info("Attempting to build and run samplebinding using cmake.")
src_path = os.path.join(examples_dir, "samplebinding")
prepare_build_folder(src_path, "cmake")
generate_build_cmake()
- run_make()
- run_make_install()
+ run_ninja()
+ run_ninja_install()
execute_script(os.path.join(src_path, "main.py"))
log.info("Attempting to build scriptableapplication using cmake.")
src_path = os.path.join(examples_dir, "scriptableapplication")
prepare_build_folder(src_path, "cmake")
generate_build_cmake()
- run_make()
-
- log.info("Attempting to build scriptableapplication using qmake.")
- src_path = os.path.join(examples_dir, "scriptableapplication")
- prepare_build_folder(src_path, "qmake")
- generate_build_qmake()
- run_make()
-
-
-def run_wheel_tests():
- wheels_dir = get_wheels_dir()
- py_version = sys.version_info[0]
-
- log.info("Attempting to install wheels.\n")
- try_install_wheels(wheels_dir, py_version)
+ run_ninja()
+
+ if sys.version_info[:2] >= (3, 7):
+ log.info("Checking Python Interface Files in Python 3 with all features selected")
+ with tempfile.TemporaryDirectory() as tmpdirname:
+ src_path = Path(tmpdirname) / "pyi_test"
+ pyi_script_dir = Path(setup_script_dir) / "sources" / "pyside6" / "PySide6" / "support"
+ execute_script(
+ pyi_script_dir / "generate_pyi.py",
+ "all",
+ "--outpath",
+ src_path,
+ "--feature",
+ "snake_case",
+ "true_property",
+ )
+ from PySide6 import __all__ as modules
+
+ for modname in modules:
+ # PYSIDE-1735: pyi files are no longer compatible with Python.
+ # XXX Maybe add a test with Mypy here?
+ pass # execute_script(src_path / f"{modname}.pyi")
+
+
+def run_wheel_tests(install_wheels, wheels_dir_name):
+ wheels_dir = get_wheels_dir(wheels_dir_name)
+ py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
+
+ if install_wheels:
+ log.info("Attempting to install wheels.\n")
+ try_install_wheels(wheels_dir, py_version)
log.info("Attempting to build examples.\n")
- try_build_examples()
+ bin_dir = os.fspath(Path(sys.executable).parent)
+ path = os.environ["PATH"]
+ if bin_dir not in path:
+ log.info(f"Adding {bin_dir} to PATH...")
+ os.environ["PATH"] = f"{bin_dir}{os.pathsep}{path}"
+ try_build_examples()
log.info("All tests passed!")
if __name__ == "__main__":
- run_wheel_tests()
+ parser = ArgumentParser(description="wheel_tester", formatter_class=RawTextHelpFormatter)
+ parser.add_argument(
+ "--no-install-wheels",
+ "-n",
+ action="store_true",
+ help="Do not install wheels" " (for developer builds with virtualenv)",
+ )
+ parser.add_argument("--qmake", type=str, help="Path to qmake")
+ parser.add_argument("--cmake", type=str, help="Path to cmake")
+ parser.add_argument("--wheels-dir", type=str, help="Path to where the wheels are",
+ default="dist")
+ parser.add_argument("--new", action="store_true", help="Option to test new wheels")
+ options = parser.parse_args()
+ QMAKE_PATH = find_executable("qmake", options.qmake)
+ CMAKE_PATH = find_executable("cmake", options.cmake)
+ NEW_WHEELS = options.new
+
+ run_wheel_tests(not options.no_install_wheels, options.wheels_dir)