############################################################################# ## ## 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$ ## ############################################################################# """ This script is used by Coin (coin_test_instructions.py specifically) to test installation of generated wheels, and test building of the "buildable" examples samplebinding and scriptableapplication. It can also be invoked regularly from the command line via python testing/wheel_tester.py --qmake=some-value --cmake=some-value The qmake and cmake arguments can also be omitted, and they will be looked up in your PATH. Make sure that some generated wheels already exist in the dist/ directory (e.g. setup.py bdist_wheel was already executed). """ from __future__ import print_function, absolute_import from argparse import ArgumentParser, RawTextHelpFormatter import os, sys try: this_file = __file__ except NameError: this_file = sys.argv[0] this_file = os.path.abspath(this_file) this_dir = os.path.dirname(this_file) setup_script_dir = os.path.abspath(os.path.join(this_dir, '..')) sys.path.append(setup_script_dir) from build_scripts.options import OPTION 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) def find_executable_qmake(): return find_executable('qmake', OPTION["QMAKE"]) def find_executable_cmake(): return find_executable('cmake', OPTION["CMAKE"]) def find_executable(executable, command_line_value): value = command_line_value option_str = '--{}'.format(executable) if value: log.info("{} option given: {}".format(option_str, value)) if not os.path.exists(value): raise RuntimeError("No executable exists at: {}".format(value)) else: log.info("No {} option given, trying to find {} in PATH.".format(option_str, executable)) paths = find_glob_in_path(executable) log.info("{} executables found in PATH: {}".format(executable, paths)) if not paths: raise RuntimeError( "No {} option was specified and no {} was found " "in PATH.".format(option_str, executable)) else: value = paths[0] log.info("Using {} found in PATH: {}".format(executable, value)) log.info("") return value QMAKE_PATH = find_executable_qmake() CMAKE_PATH = find_executable_cmake() def get_wheels_dir(): return os.path.join(setup_script_dir, "dist") def get_examples_dir(): return os.path.join(setup_script_dir, "examples") def package_prefix_names(): # Note: shiboken2_generator is not needed for compile_using_pyinstaller, # but building modules with cmake needs it. return ["shiboken2", "shiboken2_generator", "PySide2"] def clean_egg_info(): # After a successful bdist_wheel build, some .egg-info directories # are left over, which confuse pip when invoking it via # python -m pip, making pip think that the packages are already # installed in the root source directory. # Clean up the .egg-info directories to fix this, it should be # safe to do so. paths = find_files_using_glob(setup_script_dir, "*.egg-info") for p in paths: log.info("Removing {}".format(p)) rmtree(p) def install_wheel(wheel_path): log.info("Installing wheel: {}".format(wheel_path)) exit_code = run_process([sys.executable, "-m", "pip", "install", wheel_path]) log.info("") if exit_code: raise RuntimeError("Error while installing wheel {}".format(wheel_path)) def try_install_wheels(wheels_dir, py_version): clean_egg_info() all_wheels_pattern = "*.whl" all_wheels = find_files_using_glob(wheels_dir, all_wheels_pattern) if len(all_wheels) > 1: log.info("Found the following wheels in {}: ".format(wheels_dir)) for wheel in all_wheels: log.info(wheel) else: log.info("No wheels found in {}".format(wheels_dir)) log.info("") for p in package_prefix_names(): log.info("Trying to install {p}:".format(**locals())) pattern = "{}-*cp{}*.whl".format(p, int(float(py_version))) files = find_files_using_glob(wheels_dir, pattern) if files and len(files) == 1: wheel_path = files[0] install_wheel(wheel_path) elif len(files) > 1: raise RuntimeError("More than one wheel found for specific {p} version." .format(**locals())) else: raise RuntimeError("No {p} wheels compatible with Python {py_version} found " "for testing.".format(**locals())) def is_unix(): if sys.platform.startswith("linux") or sys.platform == "darwin": return True return False def generate_build_cmake(): args = [CMAKE_PATH] if is_unix(): args.extend(["-G", "Unix Makefiles"]) else: args.extend(["-G", "NMake Makefiles"]) args.append("-DCMAKE_BUILD_TYPE=Release") args.append("-Dpython_interpreter={}".format(sys.executable)) # Specify prefix path so find_package(Qt5) works. qmake_dir = os.path.abspath(os.path.join(os.path.dirname(QMAKE_PATH), "..")) args.append("-DCMAKE_PREFIX_PATH={}".format(qmake_dir)) args.append("..") exit_code = run_process(args) if exit_code: raise RuntimeError("Failure while running cmake.") log.info("") def generate_build_qmake(): exit_code = run_process([QMAKE_PATH, "..", "python_interpreter={}".format(sys.executable)]) if exit_code: raise RuntimeError("Failure while running qmake.") log.info("") def 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("PYINST: See the error message above.") print() for line in run_process_output([sys.executable, "-m", "pip", "list"]): print("PyInstaller pip list: ", line) print() raise(RuntimeError(msg)) def compile_using_pyinstaller(): src_path = 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. 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" else: executable = "nmake" args.append(executable) exit_code = run_process(args) if exit_code: raise RuntimeError("Failure while running {}.".format(executable)) log.info("") def run_make_install(): args = [] if is_unix(): executable = "make" else: executable = "nmake" args.append(executable) args.append("install") exit_code = run_process(args) if exit_code: raise RuntimeError("Failed while running {} install.".format(executable)) log.info("") def 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)) log.info("") def execute_script(script_path): args = [sys.executable, script_path] exit_code = run_process(args) if exit_code: raise RuntimeError("Failure while executing script: {}".format(script_path)) log.info("") def prepare_build_folder(src_path, build_folder_name): build_path = os.path.join(src_path, build_folder_name) # The script can be called for both Python 2 and Python 3 wheels, so # preparing a build folder should clean any previous existing build. if os.path.exists(build_path): log.info("Removing {}".format(build_path)) rmtree(build_path) log.info("Creating {}".format(build_path)) os.makedirs(build_path) os.chdir(build_path) def try_build_examples(): examples_dir = get_examples_dir() # 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")) log.info("Attempting to build and run samplebinding using cmake.") src_path = os.path.join(examples_dir, "samplebinding") prepare_build_folder(src_path, "cmake") generate_build_cmake() run_make() run_make_install() execute_script(os.path.join(src_path, "main.py")) log.info("Attempting to build scriptableapplication using cmake.") src_path = os.path.join(examples_dir, "scriptableapplication") prepare_build_folder(src_path, "cmake") generate_build_cmake() run_make() log.info("Attempting to build scriptableapplication using qmake.") src_path = os.path.join(examples_dir, "scriptableapplication") prepare_build_folder(src_path, "qmake") generate_build_qmake() run_make() def run_wheel_tests(install_wheels): wheels_dir = get_wheels_dir() py_version = "{v.major}.{v.minor}".format(v=sys.version_info) 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() log.info("All tests passed!") if __name__ == "__main__": 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)') options = parser.parse_args() run_wheel_tests(not options.no_install_wheels)