diff options
Diffstat (limited to 'build_scripts')
-rw-r--r-- | build_scripts/__init__.py | 38 | ||||
-rw-r--r-- | build_scripts/main.py | 1300 | ||||
-rw-r--r-- | build_scripts/options.py | 80 | ||||
-rw-r--r-- | build_scripts/platforms/__init__.py | 38 | ||||
-rw-r--r-- | build_scripts/platforms/linux.py | 111 | ||||
-rw-r--r-- | build_scripts/platforms/macos.py | 156 | ||||
-rw-r--r-- | build_scripts/platforms/unix.py | 167 | ||||
-rw-r--r-- | build_scripts/platforms/windows_desktop.py | 326 | ||||
-rw-r--r-- | build_scripts/qtinfo.py | 237 | ||||
-rw-r--r-- | build_scripts/utils.py | 1075 |
10 files changed, 3528 insertions, 0 deletions
diff --git a/build_scripts/__init__.py b/build_scripts/__init__.py new file mode 100644 index 000000000..f1ebb00d9 --- /dev/null +++ b/build_scripts/__init__.py @@ -0,0 +1,38 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python project. +## +## $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$ +## +############################################################################# diff --git a/build_scripts/main.py b/build_scripts/main.py new file mode 100644 index 000000000..446a7efd0 --- /dev/null +++ b/build_scripts/main.py @@ -0,0 +1,1300 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python project. +## +## $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 +from distutils.version import LooseVersion + +import os +import time +from .utils import memoize, get_python_dict +from .options import * + +setup_script_dir = os.getcwd() +build_scripts_dir = os.path.join(setup_script_dir, 'build_scripts') +setup_py_path = os.path.join(setup_script_dir, "setup.py") + +@memoize +def get_package_timestamp(): + return int(time.time()) + +@memoize +def get_package_version(): + """ Returns the version string for the PySide2 package. """ + pyside_version_py = os.path.join( + setup_script_dir, "sources", "pyside2", "pyside_version.py") + d = get_python_dict(pyside_version_py) + + final_version = "{}.{}.{}".format( + d['major_version'], d['minor_version'], d['patch_version']) + pre_release_version_type = d['pre_release_version_type'] + pre_release_version = d['pre_release_version'] + if pre_release_version and pre_release_version: + final_version += pre_release_version_type + pre_release_version + + # Add the current timestamp to the version number, to suggest it + # is a development snapshot build. + if OPTION_SNAPSHOT_BUILD: + final_version += ".dev{}".format(get_package_timestamp()) + return final_version + +# Buildable extensions. +containedModules = ['shiboken2', 'pyside2', 'pyside2-tools'] + +# Git submodules: ["submodule_name", +# "location_relative_to_sources_folder"] +submodules = [["pyside2-tools"]] + +pyside_package_dir_name = "pyside_package" + +try: + import setuptools +except ImportError: + from ez_setup import use_setuptools + use_setuptools() + +import sys +import platform +import re +import fnmatch + +import difflib # for a close match of dirname and module +import functools + +from distutils import log +from distutils.errors import DistutilsOptionError +from distutils.errors import DistutilsSetupError +from distutils.sysconfig import get_config_var +from distutils.sysconfig import get_python_lib +from distutils.spawn import find_executable +from distutils.command.build import build as _build +from distutils.command.build_ext import build_ext as _build_ext +from distutils.util import get_platform + +from setuptools import setup, Extension +from setuptools.command.install import install as _install +from setuptools.command.install_lib import install_lib as _install_lib +from setuptools.command.bdist_egg import bdist_egg as _bdist_egg +from setuptools.command.develop import develop as _develop +from setuptools.command.build_py import build_py as _build_py + +wheel_module_exists = False +try: + from wheel.bdist_wheel import bdist_wheel as _bdist_wheel + from wheel.bdist_wheel import safer_name as _safer_name + wheel_module_exists = True +except ImportError: + pass + +from .qtinfo import QtInfo +from .utils import rmtree, detectClang, copyfile, copydir, run_process_output, run_process +from .utils import update_env_path, init_msvc_env, filter_match, macos_fix_rpaths_for_library +from .platforms.unix import prepare_packages_posix +from .platforms.windows_desktop import prepare_packages_win32 + +from textwrap import dedent + +# make sure that setup.py is run with an allowed python version +def check_allowed_python_version(): + import re + pattern = "'Programming Language :: Python :: (\d+)\.(\d+)'" + supported = [] + with open(setup_py_path) as setup: + for line in setup.readlines(): + found = re.search(pattern, line) + if found: + major = int(found.group(1)) + minor = int(found.group(2)) + supported.append( (major, minor) ) + this_py = sys.version_info[:2] + if this_py not in supported: + print("only these python versions are supported:", supported) + sys.exit(1) + +check_allowed_python_version() + +qtSrcDir = '' + +# This is used automatically by distutils.command.install object, to +# specify final installation location. +OPTION_FINAL_INSTALL_PREFIX = option_value("prefix") + + +if OPTION_QT_VERSION is None: + OPTION_QT_VERSION = "5" +if OPTION_QMAKE is None: + OPTION_QMAKE = find_executable("qmake-qt5") +if OPTION_QMAKE is None: + OPTION_QMAKE = find_executable("qmake") + +# make qtinfo.py independent of relative paths. +if OPTION_QMAKE is not None and os.path.exists(OPTION_QMAKE): + OPTION_QMAKE = os.path.abspath(OPTION_QMAKE) +if OPTION_CMAKE is not None and os.path.exists(OPTION_CMAKE): + OPTION_CMAKE = os.path.abspath(OPTION_CMAKE) + +QMAKE_COMMAND = None +# Checking whether qmake executable exists +if OPTION_QMAKE is not None and os.path.exists(OPTION_QMAKE): + # Looking whether qmake path is a link and whether the link exists + if os.path.islink(OPTION_QMAKE) and os.path.lexists(OPTION_QMAKE): + # Set -qt=X here. + if "qtchooser" in os.readlink(OPTION_QMAKE): + QMAKE_COMMAND = [OPTION_QMAKE, "-qt={}".format(OPTION_QT_VERSION)] +if not QMAKE_COMMAND: + QMAKE_COMMAND = [OPTION_QMAKE] + +if len(QMAKE_COMMAND) == 0 or QMAKE_COMMAND[0] is None: + print("qmake could not be found.") + sys.exit(1) +if not os.path.exists(QMAKE_COMMAND[0]): + print("'{}' does not exist.".format(QMAKE_COMMAND[0])) + sys.exit(1) +if OPTION_CMAKE is None: + OPTION_CMAKE = find_executable("cmake") + +if OPTION_CMAKE is None: + print("cmake could not be found.") + sys.exit(1) +if not os.path.exists(OPTION_CMAKE): + print("'{}' does not exist.".format(OPTION_CMAKE)) + sys.exit(1) + +if sys.platform == "win32": + if OPTION_MAKESPEC is None: + OPTION_MAKESPEC = "msvc" + if not OPTION_MAKESPEC in ["msvc", "mingw"]: + print("Invalid option --make-spec. Available values are {}".format( + ["msvc", "mingw"])) + sys.exit(1) +else: + if OPTION_MAKESPEC is None: + OPTION_MAKESPEC = "make" + if not OPTION_MAKESPEC in ["make"]: + print("Invalid option --make-spec. Available values are {}".format( + ["make"])) + sys.exit(1) + +if OPTION_JOBS: + if sys.platform == 'win32' and OPTION_NO_JOM: + print("Option --jobs can only be used with jom on Windows.") + sys.exit(1) + else: + if not OPTION_JOBS.startswith('-j'): + OPTION_JOBS = '-j' + OPTION_JOBS +else: + OPTION_JOBS = '' + +def is_debug_python(): + return getattr(sys, "gettotalrefcount", None) is not None + +# Return a prefix suitable for the _install/_build directory +def prefix(): + virtualEnvName = os.environ.get('VIRTUAL_ENV', None) + if virtualEnvName is not None: + name = os.path.basename(virtualEnvName) + else: + name = "pyside" + name += str(sys.version_info[0]) + if OPTION_DEBUG: + name += 'd' + if is_debug_python(): + name += 'p' + return name + +# Initialize, pull and checkout submodules +def prepareSubModules(): + print("Initializing submodules for PySide2 version: {}".format( + get_package_version())) + submodules_dir = os.path.join(setup_script_dir, "sources") + + # Create list of [name, desired branch, absolute path, desired + # branch] and determine whether all submodules are present + needInitSubModules = False + + for m in submodules: + module_name = m[0] + module_dir = m[1] if len(m) > 1 else '' + module_dir = os.path.join(submodules_dir, module_dir, module_name) + # Check for non-empty directory (repository checked out) + if not os.listdir(module_dir): + needInitSubModules = True + break + + if needInitSubModules: + git_update_cmd = ["git", "submodule", "update", "--init"] + if run_process(git_update_cmd) != 0: + m = ("Failed to initialize the git submodules: " + "update --init failed") + raise DistutilsSetupError(m) + git_pull_cmd = ["git", "submodule", "foreach", "git", "fetch", "--all"] + if run_process(git_pull_cmd) != 0: + m = ("Failed to initialize the git submodules: " + "git fetch --all failed") + raise DistutilsSetupError(m) + else: + print("All submodules present.") + + git_update_cmd = ["git", "submodule", "update"] + if run_process(git_update_cmd) != 0: + m = "Failed to checkout the correct git submodules SHA1s." + raise DistutilsSetupError(m) + +# Single global instance of QtInfo to be used later in multiple code +# paths. +qtinfo = QtInfo(QMAKE_COMMAND) + +def get_qt_version(): + qt_version = qtinfo.version + + if not qt_version: + log.error("Failed to query the Qt version with qmake {0}".format( + self.qtinfo.qmake_command)) + sys.exit(1) + + if LooseVersion(qtinfo.version) < LooseVersion("5.7"): + log.error("Incompatible Qt version detected: {}. " + "A Qt version >= 5.7 is required.".format(qt_version)) + sys.exit(1) + + return qt_version + +def prepareBuild(): + if (os.path.isdir(".git") and not OPTION_IGNOREGIT and + not OPTION_ONLYPACKAGE and not OPTION_REUSE_BUILD): + prepareSubModules() + # Clean up temp and package folders + for n in [pyside_package_dir_name, "build"]: + d = os.path.join(setup_script_dir, n) + if os.path.isdir(d): + print("Removing {}".format(d)) + try: + rmtree(d) + except Exception as e: + print('***** problem removing "{}"'.format(d)) + print('ignored error: {}'.format(e)) + # Prepare package folders + ppdn = pyside_package_dir_name + absolute_paths = [os.path.join(ppdn, "PySide2"), + os.path.join(ppdn, "pyside2uic")] + for pkg in absolute_paths: + pkg_dir = os.path.join(setup_script_dir, pkg) + os.makedirs(pkg_dir) + # locate Qt sources for the documentation + if OPTION_QT_SRC is None: + installPrefix = qtinfo.prefix_dir + if installPrefix: + global qtSrcDir + # In-source, developer build + if installPrefix.endswith("qtbase"): + qtSrcDir = installPrefix + else: # SDK: Use 'Src' directory + qtSrcDir = os.path.join(os.path.dirname(installPrefix), + 'Src', 'qtbase') + +class pyside_install(_install): + def __init__(self, *args, **kwargs): + _install.__init__(self, *args, **kwargs) + + def initialize_options (self): + _install.initialize_options(self) + + if sys.platform == 'darwin': + # Because we change the plat_name to include a correct + # deployment target on macOS distutils thinks we are + # cross-compiling, and throws an exception when trying to + # execute setup.py install. The check looks like this + # if self.warn_dir and build_plat != get_platform(): + # raise DistutilsPlatformError("Can't install when " + # "cross-compiling") + # Obviously get_platform will return the old deployment + # target. The fix is to disable the warn_dir flag, which + # was created for bdist_* derived classes to override, for + # similar cases. + self.warn_dir = False + + def run(self): + _install.run(self) + log.info('*** Install completed') + +class pyside_develop(_develop): + + def __init__(self, *args, **kwargs): + _develop.__init__(self, *args, **kwargs) + + def run(self): + self.run_command("build") + _develop.run(self) + +class pyside_bdist_egg(_bdist_egg): + + def __init__(self, *args, **kwargs): + _bdist_egg.__init__(self, *args, **kwargs) + + def run(self): + self.run_command("build") + _bdist_egg.run(self) + +class pyside_build_ext(_build_ext): + + def __init__(self, *args, **kwargs): + _build_ext.__init__(self, *args, **kwargs) + + def run(self): + pass + +if wheel_module_exists: + class pyside_build_wheel(_bdist_wheel): + def __init__(self, *args, **kwargs): + _bdist_wheel.__init__(self, *args, **kwargs) + + @property + def wheel_dist_name(self): + # Slightly modified version of wheel's wheel_dist_name + # method, to add the Qt version as well. + # Example: + # PySide2-5.6-5.6.4-cp27-cp27m-macosx_10_10_intel.whl + # The PySide2 version is "5.6". + # The Qt version built against is "5.6.4". + qt_version = get_qt_version() + package_version = get_package_version() + wheel_version = "{}-{}".format(package_version, qt_version) + components = (_safer_name(self.distribution.get_name()), + wheel_version) + if self.build_number: + components += (self.build_number,) + return '-'.join(components) + + def finalize_options(self): + if sys.platform == 'darwin': + # Override the platform name to contain the correct + # minimum deployment target. + # This is used in the final wheel name. + self.plat_name = pyside_build.macos_plat_name() + _bdist_wheel.finalize_options(self) + +# pyside_build_py and pyside_install_lib are reimplemented to preserve +# symlinks when distutils / setuptools copy files to various +# directories through the different build stages. +class pyside_build_py(_build_py): + + def __init__(self, *args, **kwargs): + _build_py.__init__(self, *args, **kwargs) + + def build_package_data(self): + """Copies files from pyside_package into build/xxx directory""" + + for package, src_dir, build_dir, filenames in self.data_files: + for filename in filenames: + target = os.path.join(build_dir, filename) + self.mkpath(os.path.dirname(target)) + srcfile = os.path.abspath(os.path.join(src_dir, filename)) + # Using our own copyfile makes sure to preserve symlinks. + copyfile(srcfile, target) + +class pyside_install_lib(_install_lib): + + def __init__(self, *args, **kwargs): + _install_lib.__init__(self, *args, **kwargs) + + def install(self): + """ + Installs files from build/xxx directory into final + site-packages/PySide2 directory. + """ + + if os.path.isdir(self.build_dir): + # Using our own copydir makes sure to preserve symlinks. + outfiles = copydir(os.path.abspath(self.build_dir), + os.path.abspath(self.install_dir)) + else: + self.warn("'{}' does not exist -- " + "no Python modules to install".format(self.build_dir)) + return + return outfiles + +class pyside_build(_build): + + def __init__(self, *args, **kwargs): + _build.__init__(self, *args, **kwargs) + + def finalize_options(self): + os_name_backup = os.name + if sys.platform == 'darwin': + self.plat_name = pyside_build.macos_plat_name() + # This is a hack to circumvent the dubious check in + # distutils.commands.build -> finalize_options, which only + # allows setting the plat_name for windows NT. + # That is not the case for the wheel module though (which + # does allow setting plat_name), so we circumvent by faking + # the os name when finalizing the options, and then + # restoring the original os name. + os.name = "nt" + + _build.finalize_options(self) + + if sys.platform == 'darwin': + os.name = os_name_backup + + def initialize_options(self): + _build.initialize_options(self) + self.make_path = None + self.make_generator = None + self.debug = False + self.script_dir = None + self.sources_dir = None + self.build_dir = None + self.install_dir = None + self.py_executable = None + self.py_include_dir = None + self.py_library = None + self.py_version = None + self.build_type = "Release" + self.qtinfo = None + self.build_tests = False + + def run(self): + prepareBuild() + platform_arch = platform.architecture()[0] + log.info("Python architecture is {}".format(platform_arch)) + + build_type = "Debug" if OPTION_DEBUG else "Release" + if OPTION_RELWITHDEBINFO: + build_type = 'RelWithDebInfo' + + # Check env + make_path = None + make_generator = None + if not OPTION_ONLYPACKAGE: + if OPTION_MAKESPEC == "make": + make_name = "make" + make_generator = "Unix Makefiles" + elif OPTION_MAKESPEC == "msvc": + nmake_path = find_executable("nmake") + if nmake_path is None or not os.path.exists(nmake_path): + log.info("nmake not found. " + "Trying to initialize the MSVC env...") + init_msvc_env(platform_arch, build_type) + nmake_path = find_executable("nmake") + assert(nmake_path is not None and + os.path.exists(nmake_path)) + jom_path = None if OPTION_NO_JOM else find_executable("jom") + if jom_path is not None and os.path.exists(jom_path): + log.info("jom was found in {}".format(jom_path)) + make_name = "jom" + make_generator = "NMake Makefiles JOM" + else: + log.info("nmake was found in {}".format(nmake_path)) + make_name = "nmake" + make_generator = "NMake Makefiles" + elif OPTION_MAKESPEC == "mingw": + make_name = "mingw32-make" + make_generator = "MinGW Makefiles" + else: + raise DistutilsSetupError( + "Invalid option --make-spec.") + make_path = find_executable(make_name) + if make_path is None or not os.path.exists(make_path): + raise DistutilsSetupError("You need the program '{}' on your " + "system path to compile PySide2.".format(make_name)) + + if OPTION_CMAKE is None or not os.path.exists(OPTION_CMAKE): + raise DistutilsSetupError( + "Failed to find cmake." + " Please specify the path to cmake with " + "--cmake parameter.") + + if OPTION_QMAKE is None or not os.path.exists(OPTION_QMAKE): + raise DistutilsSetupError( + "Failed to find qmake." + " Please specify the path to qmake with --qmake parameter.") + + # Prepare parameters + py_executable = sys.executable + py_version = "{}.{}".format(sys.version_info[0], sys.version_info[1]) + py_include_dir = get_config_var("INCLUDEPY") + py_libdir = get_config_var("LIBDIR") + py_prefix = get_config_var("prefix") + if not py_prefix or not os.path.exists(py_prefix): + py_prefix = sys.prefix + if sys.platform == "win32": + py_scripts_dir = os.path.join(py_prefix, "Scripts") + else: + py_scripts_dir = os.path.join(py_prefix, "bin") + if py_libdir is None or not os.path.exists(py_libdir): + if sys.platform == "win32": + py_libdir = os.path.join(py_prefix, "libs") + else: + py_libdir = os.path.join(py_prefix, "lib") + if py_include_dir is None or not os.path.exists(py_include_dir): + if sys.platform == "win32": + py_include_dir = os.path.join(py_prefix, "include") + else: + py_include_dir = os.path.join(py_prefix, + "include/python{}".format(py_version)) + dbgPostfix = "" + if build_type == "Debug": + dbgPostfix = "_d" + if sys.platform == "win32": + if OPTION_MAKESPEC == "mingw": + static_lib_name = "libpython{}{}.a".format( + py_version.replace(".", ""), dbgPostfix) + py_library = os.path.join(py_libdir, static_lib_name) + else: + python_lib_name = "python{}{}.lib".format( + py_version.replace(".", ""), dbgPostfix) + py_library = os.path.join(py_libdir, python_lib_name) + else: + lib_exts = ['.so'] + if sys.platform == 'darwin': + lib_exts.append('.dylib') + if sys.version_info[0] > 2: + lib_suff = getattr(sys, 'abiflags', None) + else: # Python 2 + lib_suff = '' + lib_exts.append('.so.1') + # Suffix for OpenSuSE 13.01 + lib_exts.append('.so.1.0') + # static library as last gasp + lib_exts.append('.a') + + if sys.version_info[0] == 2 and dbgPostfix: + # For Python2 add a duplicate set of extensions + # combined with the dbgPostfix, so we test for both the + # debug version of the lib and the normal one. + # This allows a debug PySide2 to be built with a + # non-debug Python. + lib_exts = [dbgPostfix + e for e in lib_exts] + lib_exts + + python_library_found = False + libs_tried = [] + for lib_ext in lib_exts: + lib_name = "libpython{}{}{}".format(py_version, lib_suff, + lib_ext) + py_library = os.path.join(py_libdir, lib_name) + if os.path.exists(py_library): + python_library_found = True + break + libs_tried.append(py_library) + else: + # At least on macOS 10.11, the system Python 2.6 does + # not include a symlink to the framework file disguised + # as a .dylib file, thus finding the library would + # fail. + # Manually check if a framework file "Python" exists in + # the Python framework bundle. + if sys.platform == 'darwin' and sys.version_info[:2] == (2, 6): + # These manipulations essentially transform + # /System/Library/Frameworks/Python.framework/Versions/2.6/lib + # to + # /System/Library/Frameworks/Python.framework/Versions/2.6/Python + possible_framework_path = os.path.realpath( + os.path.join(py_libdir, '..')) + possible_framework_version = os.path.basename( + possible_framework_path) + possible_framework_library = os.path.join( + possible_framework_path, 'Python') + + if (possible_framework_version == '2.6' and + os.path.exists(possible_framework_library)): + py_library = possible_framework_library + python_library_found = True + else: + libs_tried.append(possible_framework_library) + + # Try to find shared libraries which have a multi arch + # suffix. + if not python_library_found: + py_multiarch = get_config_var("MULTIARCH") + if py_multiarch and not python_library_found: + try_py_libdir = os.path.join(py_libdir, py_multiarch) + libs_tried = [] + for lib_ext in lib_exts: + lib_name = "libpython{}{}{}".format( + py_version, lib_suff, lib_ext) + py_library = os.path.join(try_py_libdir, lib_name) + if os.path.exists(py_library): + py_libdir = try_py_libdir + python_library_found = True + break + libs_tried.append(py_library) + + if not python_library_found: + raise DistutilsSetupError( + "Failed to locate the Python library with {}".format( + ", ".join(libs_tried))) + + if py_library.endswith('.a'): + # Python was compiled as a static library + log.error("Failed to locate a dynamic Python library, " + "using {}".format(py_library)) + + self.qtinfo = qtinfo + qt_dir = os.path.dirname(OPTION_QMAKE) + qt_version = get_qt_version() + + # Update the PATH environment variable + additionalPaths = [py_scripts_dir, qt_dir] + + # Add Clang to path for Windows. + # Revisit once Clang is bundled with Qt. + if (sys.platform == "win32" and + LooseVersion(self.qtinfo.version) >= LooseVersion("5.7.0")): + clangDir = detectClang() + if clangDir[0]: + clangBinDir = os.path.join(clangDir[0], 'bin') + if not clangBinDir in os.environ.get('PATH'): + log.info("Adding {} as detected by {} to PATH".format( + clangBinDir, clangDir[1])) + additionalPaths.append(clangBinDir) + else: + raise DistutilsSetupError("Failed to detect Clang when checking " + "LLVM_INSTALL_DIR, CLANG_INSTALL_DIR, llvm-config") + + update_env_path(additionalPaths) + + build_name = "py{}-qt{}-{}-{}".format(py_version, qt_version, + platform.architecture()[0], build_type.lower()) + + script_dir = setup_script_dir + sources_dir = os.path.join(script_dir, "sources") + build_dir = os.path.join(script_dir, prefix() + "_build", + "{}".format(build_name)) + install_dir = os.path.join(script_dir, prefix() + "_install", + "{}".format(build_name)) + + # Try to ensure that tools built by this script (such as shiboken2) + # are found before any that may already be installed on the system. + update_env_path([os.path.join(install_dir, 'bin')]) + + # Tell cmake to look here for *.cmake files + os.environ['CMAKE_PREFIX_PATH'] = install_dir + + self.make_path = make_path + self.make_generator = make_generator + self.debug = OPTION_DEBUG + self.script_dir = script_dir + self.pyside_package_dir = os.path.join(self.script_dir, + pyside_package_dir_name) + self.sources_dir = sources_dir + self.build_dir = build_dir + self.install_dir = install_dir + self.py_executable = py_executable + self.py_include_dir = py_include_dir + self.py_library = py_library + self.py_version = py_version + self.build_type = build_type + self.site_packages_dir = get_python_lib(1, 0, prefix=install_dir) + self.build_tests = OPTION_BUILDTESTS + + setuptools_install_prefix = get_python_lib(1) + if OPTION_FINAL_INSTALL_PREFIX: + setuptools_install_prefix = OPTION_FINAL_INSTALL_PREFIX + + # Save the shiboken build dir path for clang deployment + # purposes. + self.shiboken_build_dir = os.path.join(self.build_dir, "shiboken2") + + log.info("=" * 30) + log.info("Package version: {}".format(get_package_version())) + log.info("Build type: {}".format(self.build_type)) + log.info("Build tests: {}".format(self.build_tests)) + log.info("-" * 3) + log.info("Make path: {}".format(self.make_path)) + log.info("Make generator: {}".format(self.make_generator)) + log.info("Make jobs: {}".format(OPTION_JOBS)) + log.info("-" * 3) + + log.info("setup.py directory: {}".format(self.script_dir)) + log.info("Build scripts directory: {}".format(build_scripts_dir)) + log.info("Sources directory: {}".format(self.sources_dir)) + + log.info(dedent(""" + Building PySide2 will create and touch directories + in the following order: + make build directory (py*_build/*/*) -> + make install directory (py*_install/*/*) -> + {} directory (pyside_package/*) -> + setuptools build directory (build/*/*) -> + setuptools install directory + (usually path-installed-python/lib/python*/site-packages/*) + """).format(pyside_package_dir_name)) + + log.info("make build directory: {}".format(self.build_dir)) + log.info("make install directory: {}".format(self.install_dir)) + log.info("{} directory: {}".format(pyside_package_dir_name, + self.pyside_package_dir)) + log.info("setuptools build directory: {}".format( + os.path.join(self.script_dir, "build"))) + log.info("setuptools install directory: {}".format( + setuptools_install_prefix)) + log.info("make-installed site-packages directory: {} \n" + " (only relevant for copying files from " + "'make install directory' to '{} directory'".format( + self.site_packages_dir, pyside_package_dir_name)) + log.info("-" * 3) + log.info("Python executable: {}".format(self.py_executable)) + log.info("Python includes: {}".format(self.py_include_dir)) + log.info("Python library: {}".format(self.py_library)) + log.info("Python prefix: {}".format(py_prefix)) + log.info("Python scripts: {}".format(py_scripts_dir)) + log.info("-" * 3) + log.info("Qt qmake: {}".format(self.qtinfo.qmake_command)) + log.info("Qt version: {}".format(self.qtinfo.version)) + log.info("Qt bins: {}".format(self.qtinfo.bins_dir)) + log.info("Qt docs: {}".format(self.qtinfo.docs_dir)) + log.info("Qt plugins: {}".format(self.qtinfo.plugins_dir)) + log.info("-" * 3) + if sys.platform == 'win32': + log.info("OpenSSL dll directory: {}".format(OPTION_OPENSSL)) + if sys.platform == 'darwin': + pyside_macos_deployment_target = ( + pyside_build.macos_pyside_min_deployment_target() + ) + log.info("MACOSX_DEPLOYMENT_TARGET set to: {}".format( + pyside_macos_deployment_target)) + log.info("=" * 30) + + # Prepare folders + if not os.path.exists(self.sources_dir): + log.info("Creating sources folder {}...".format(self.sources_dir)) + os.makedirs(self.sources_dir) + if not os.path.exists(self.build_dir): + log.info("Creating build folder {}...".format(self.build_dir)) + os.makedirs(self.build_dir) + if not os.path.exists(self.install_dir): + log.info("Creating install folder {}...".format(self.install_dir)) + os.makedirs(self.install_dir) + + if not OPTION_ONLYPACKAGE: + # Build extensions + for ext in containedModules: + self.build_extension(ext) + + if OPTION_BUILDTESTS: + # we record the latest successful build and note the + # build directory for supporting the tests. + timestamp = time.strftime('%Y-%m-%d_%H%M%S') + build_history = os.path.join(setup_script_dir, 'build_history') + unique_dir = os.path.join(build_history, timestamp) + os.makedirs(unique_dir) + fpath = os.path.join(unique_dir, 'build_dir.txt') + with open(fpath, 'w') as f: + print(build_dir, file=f) + log.info("Created {}".format(build_history)) + + if not OPTION_SKIP_PACKAGING: + # Build patchelf if needed + self.build_patchelf() + + # Prepare packages + self.prepare_packages() + + # Build packages + _build.run(self) + else: + log.info("Skipped preparing and building packages.") + log.info('*** Build completed') + + @staticmethod + def macos_qt_min_deployment_target(): + target = qtinfo.macos_min_deployment_target + + if not target: + raise DistutilsSetupError("Failed to query for Qt's " + "QMAKE_MACOSX_DEPLOYMENT_TARGET.") + return target + + @staticmethod + @memoize + def macos_pyside_min_deployment_target(): + """ + Compute and validate PySide2 MACOSX_DEPLOYMENT_TARGET value. + Candidate sources that are considered: + - setup.py provided value + - maximum value between minimum deployment target of the + Python interpreter and the minimum deployment target of + the Qt libraries. + If setup.py value is provided, that takes precedence. + Otherwise use the maximum of the above mentioned two values. + """ + python_target = get_config_var('MACOSX_DEPLOYMENT_TARGET') or None + qt_target = pyside_build.macos_qt_min_deployment_target() + setup_target = OPTION_MACOS_DEPLOYMENT_TARGET + + qt_target_split = [int(x) for x in qt_target.split('.')] + if python_target: + python_target_split = [int(x) for x in python_target.split('.')] + if setup_target: + setup_target_split = [int(x) for x in setup_target.split('.')] + + message = ("Can't set MACOSX_DEPLOYMENT_TARGET value to {} because " + "{} was built with minimum deployment target set to {}.") + # setup.py provided OPTION_MACOS_DEPLOYMENT_TARGET value takes + # precedence. + if setup_target: + if python_target and setup_target_split < python_target_split: + raise DistutilsSetupError(message.format(setup_target, + "Python", python_target)) + if setup_target_split < qt_target_split: + raise DistutilsSetupError(message.format(setup_target, + "Qt", qt_target)) + # All checks clear, use setup.py provided value. + return setup_target + + # Setup.py value not provided, + # use same value as provided by Qt. + if python_target: + maximum_target = '.'.join([str(e) for e in max(python_target_split, + qt_target_split)]) + else: + maximum_target = qt_target + return maximum_target + + @staticmethod + @memoize + def macos_plat_name(): + deployment_target = pyside_build.macos_pyside_min_deployment_target() + # Example triple "macosx-10.12-x86_64". + plat = get_platform().split("-") + plat_name = "{}-{}-{}".format(plat[0], deployment_target, plat[2]) + return plat_name + + def build_patchelf(self): + if not sys.platform.startswith('linux'): + return + log.info("Building patchelf...") + module_src_dir = os.path.join(self.sources_dir, "patchelf") + build_cmd = [ + "g++", + "{}/patchelf.cc".format(module_src_dir), + "-o", + "patchelf", + ] + if run_process(build_cmd) != 0: + raise DistutilsSetupError("Error building patchelf") + + def build_extension(self, extension): + # calculate the subrepos folder name + + log.info("Building module {}...".format(extension)) + + # Prepare folders + os.chdir(self.build_dir) + module_build_dir = os.path.join(self.build_dir, extension) + skipflag_file = module_build_dir + '-skip' + if os.path.exists(skipflag_file): + log.info("Skipping {} because {} exists".format(extension, + skipflag_file)) + return + + module_build_exists = os.path.exists(module_build_dir) + if module_build_exists: + if not OPTION_REUSE_BUILD: + log.info("Deleting module build folder {}...".format( + module_build_dir)) + try: + rmtree(module_build_dir) + except Exception as e: + print('***** problem removing "{}"'.format( + module_build_dir)) + print('ignored error: {}'.format(e)) + else: + log.info("Reusing module build folder {}...".format( + module_build_dir)) + if not os.path.exists(module_build_dir): + log.info("Creating module build folder {}...".format( + module_build_dir)) + os.makedirs(module_build_dir) + os.chdir(module_build_dir) + + module_src_dir = os.path.join(self.sources_dir, extension) + + # Build module + cmake_cmd = [ + OPTION_CMAKE, + "-G", self.make_generator, + "-DBUILD_TESTS={}".format(self.build_tests), + "-DQt5Help_DIR={}".format(self.qtinfo.docs_dir), + "-DCMAKE_BUILD_TYPE={}".format(self.build_type), + "-DCMAKE_INSTALL_PREFIX={}".format(self.install_dir), + module_src_dir + ] + cmake_cmd.append("-DPYTHON_EXECUTABLE={}".format(self.py_executable)) + cmake_cmd.append("-DPYTHON_INCLUDE_DIR={}".format(self.py_include_dir)) + cmake_cmd.append("-DPYTHON_LIBRARY={}".format(self.py_library)) + if OPTION_MODULE_SUBSET: + moduleSubSet = '' + for m in OPTION_MODULE_SUBSET.split(','): + if m.startswith('Qt'): + m = m[2:] + if moduleSubSet: + moduleSubSet += ';' + moduleSubSet += m + cmake_cmd.append("-DMODULES={}".format(moduleSubSet)) + # Add source location for generating documentation + cmake_src_dir = OPTION_QT_SRC if OPTION_QT_SRC else qtSrcDir + cmake_cmd.append("-DQT_SRC_DIR={}".format(cmake_src_dir)) + log.info("Qt Source dir: {}".format(cmake_src_dir)) + + if self.build_type.lower() == 'debug': + cmake_cmd.append("-DPYTHON_DEBUG_LIBRARY={}".format( + self.py_library)) + + if OPTION_VERBOSE_BUILD: + cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON") + + if OPTION_SANITIZE_ADDRESS: + # Some simple sanity checking. Only use at your own risk. + if (sys.platform.startswith('linux') or + sys.platform.startswith('darwin')): + cmake_cmd.append("-DSANITIZE_ADDRESS=ON") + else: + raise DistutilsSetupError("Address sanitizer can only be used " + "on Linux and macOS.") + + if extension.lower() == "pyside2": + pyside_qt_conf_prefix = '' + if OPTION_QT_CONF_PREFIX: + pyside_qt_conf_prefix = OPTION_QT_CONF_PREFIX + else: + if OPTION_STANDALONE: + pyside_qt_conf_prefix = '"Qt"' + if sys.platform == 'win32': + pyside_qt_conf_prefix = '"."' + cmake_cmd.append("-DPYSIDE_QT_CONF_PREFIX={}".format( + pyside_qt_conf_prefix)) + + # Pass package version to CMake, so this string can be + # embedded into _config.py file. + package_version = get_package_version() + cmake_cmd.append("-DPYSIDE_SETUP_PY_PACKAGE_VERSION={}".format( + package_version)) + + # In case if this is a snapshot build, also pass the + # timestamp as a separate value, because it the only + # version component that is actually generated by setup.py. + timestamp = '' + if OPTION_SNAPSHOT_BUILD: + timestamp = get_package_timestamp() + cmake_cmd.append("-DPYSIDE_SETUP_PY_PACKAGE_TIMESTAMP={}".format( + timestamp)) + + if extension.lower() == "shiboken2": + cmake_cmd.append("-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=yes") + if sys.version_info[0] > 2: + cmake_cmd.append("-DUSE_PYTHON_VERSION=3.3") + + if sys.platform == 'darwin': + if OPTION_MACOS_ARCH: + # also tell cmake which architecture to use + cmake_cmd.append("-DCMAKE_OSX_ARCHITECTURES:STRING={}".format( + OPTION_MACOS_ARCH)) + + if OPTION_MACOS_USE_LIBCPP: + # Explicitly link the libc++ standard library (useful + # for macOS deployment targets lower than 10.9). + # This is not on by default, because most libraries and + # executables on macOS <= 10.8 are linked to libstdc++, + # and mixing standard libraries can lead to crashes. + # On macOS >= 10.9 with a similar minimum deployment + # target, libc++ is linked in implicitly, thus the + # option is a no-op in those cases. + cmake_cmd.append("-DOSX_USE_LIBCPP=ON") + + if OPTION_MACOS_SYSROOT: + cmake_cmd.append("-DCMAKE_OSX_SYSROOT={}".format( + OPTION_MACOS_SYSROOT)) + else: + latest_sdk_path = run_process_output(['xcrun', + '--show-sdk-path']) + if latest_sdk_path: + latest_sdk_path = latest_sdk_path[0] + cmake_cmd.append("-DCMAKE_OSX_SYSROOT={}".format( + latest_sdk_path)) + + # Set macOS minimum deployment target (version). + # This is required so that calling + # run_process -> distutils.spawn() + # does not set its own minimum deployment target + # environment variable which is based on the python + # interpreter sysconfig value. + # Doing so could break the detected clang include paths + # for example. + deployment_target = \ + pyside_build.macos_pyside_min_deployment_target() + cmake_cmd.append("-DCMAKE_OSX_DEPLOYMENT_TARGET={}".format( + deployment_target)) + os.environ['MACOSX_DEPLOYMENT_TARGET'] = deployment_target + + if not OPTION_SKIP_CMAKE: + log.info("Configuring module {} ({})...".format(extension, + module_src_dir)) + if run_process(cmake_cmd) != 0: + raise DistutilsSetupError("Error configuring {}".format( + extension)) + else: + log.info("Reusing old configuration for module {} ({})...".format( + extension, module_src_dir)) + + log.info("Compiling module {}...".format(extension)) + cmd_make = [self.make_path] + if OPTION_JOBS: + cmd_make.append(OPTION_JOBS) + if run_process(cmd_make) != 0: + raise DistutilsSetupError("Error compiling {}".format(extension)) + + if extension.lower() == "shiboken2": + try: + # Check if sphinx is installed to proceed. + import sphinx + + log.info("Generating Shiboken documentation") + if run_process([self.make_path, "doc"]) != 0: + raise DistutilsSetupError( + "Error generating documentation for {}".format(extension)) + except ImportError: + log.info("Sphinx not found, skipping documentation build") + + + if not OPTION_SKIP_MAKE_INSTALL: + log.info("Installing module {}...".format(extension)) + # Need to wait a second, so installed file timestamps are + # older than build file timestamps. + # See https://gitlab.kitware.com/cmake/cmake/issues/16155 + # for issue details. + if sys.platform == 'darwin': + log.info("Waiting 1 second, to ensure installation is " + "successful...") + time.sleep(1) + if run_process([self.make_path, "install/fast"]) != 0: + raise DistutilsSetupError("Error pseudo installing {}".format( + extension)) + else: + log.info("Skipped installing module {}".format(extension)) + + os.chdir(self.script_dir) + + def prepare_packages(self): + try: + log.info("Preparing packages...") + vars = { + "site_packages_dir": self.site_packages_dir, + "sources_dir": self.sources_dir, + "install_dir": self.install_dir, + "build_dir": self.build_dir, + "script_dir": self.script_dir, + "pyside_package_dir": self.pyside_package_dir, + "ssl_libs_dir": OPTION_OPENSSL, + "py_version": self.py_version, + "qt_version": self.qtinfo.version, + "qt_bin_dir": self.qtinfo.bins_dir, + "qt_doc_dir": self.qtinfo.docs_dir, + "qt_lib_dir": self.qtinfo.libs_dir, + "qt_lib_execs_dir": self.qtinfo.lib_execs_dir, + "qt_plugins_dir": self.qtinfo.plugins_dir, + "qt_prefix_dir": self.qtinfo.prefix_dir, + "qt_translations_dir": self.qtinfo.translations_dir, + "qt_qml_dir": self.qtinfo.qml_dir, + } + os.chdir(self.script_dir) + + if sys.platform == "win32": + vars['dbgPostfix'] = OPTION_DEBUG and "_d" or "" + return prepare_packages_win32(self, vars) + else: + return prepare_packages_posix(self, vars) + except IOError as e: + print('setup.py/prepare_packages: ', e) + raise + + def qt_is_framework_build(self): + if os.path.isdir(self.qtinfo.headers_dir + "/../lib/QtCore.framework"): + return True + return False + + def get_built_pyside_config(self, vars): + # Get config that contains list of built modules, and + # SOVERSIONs of the built libraries. + pyside_package_dir = vars['pyside_package_dir'] + config_path = os.path.join(pyside_package_dir, "PySide2", "_config.py") + config = get_python_dict(config_path) + return config + + def is_webengine_built(self, built_modules): + return 'WebEngineWidgets' in built_modules or 'WebEngineCore' in built_modules + + def prepare_standalone_clang(self, is_win = False): + """ + Copies the libclang library to the pyside package so that + shiboken executable works. + """ + log.info('Finding path to the libclang shared library.') + cmake_cmd = [ + OPTION_CMAKE, + "-L", # Lists variables + "-N", # Just inspects the cache (faster) + "--build", # Specifies the build dir + self.shiboken_build_dir + ] + out = run_process_output(cmake_cmd) + lines = [s.strip() for s in out] + pattern = re.compile(r"CLANG_LIBRARY:FILEPATH=(.+)$") + + clang_lib_path = None + for line in lines: + match = pattern.search(line) + if match: + clang_lib_path = match.group(1) + break + + if not clang_lib_path: + raise RuntimeError("Could not finding location of libclang " + "library from CMake cache.") + + if is_win: + # clang_lib_path points to the static import library + # (lib/libclang.lib), whereas we want to copy the shared + # library (bin/libclang.dll). + clang_lib_path = re.sub(r'lib/libclang.lib$', 'bin/libclang.dll', + clang_lib_path) + + # Path to directory containing clang. + clang_lib_dir = os.path.dirname(clang_lib_path) + + # The name of the clang file found by CMake. + basename = os.path.basename(clang_lib_path) + + # We want to copy the library and all the symlinks for now, + # thus the wildcard. + clang_filter = basename + "*" + + # Destination is the package folder near the other extension + # modules. + destination_dir = "{}/PySide2".format(os.path.join(self.script_dir, + 'pyside_package')) + if os.path.exists(clang_lib_path): + log.info('Copying libclang shared library to the pyside package.') + + copydir(clang_lib_dir, destination_dir, + filter=[clang_filter], + recursive=False) + else: + raise RuntimeError("Error copying libclang library " + "from {} to {}. ".format( + clang_lib_path, destination_dir)) + + def update_rpath(self, package_path, executables): + if sys.platform.startswith('linux'): + pyside_libs = [lib for lib in os.listdir( + package_path) if filter_match(lib, ["*.so", "*.so.*"])] + + patchelf_path = os.path.join(self.script_dir, "patchelf") + + def rpath_cmd(srcpath): + final_rpath = '' + # Command line rpath option takes precedence over + # automatically added one. + if OPTION_RPATH_VALUES: + final_rpath = OPTION_RPATH_VALUES + else: + # Add rpath values pointing to $ORIGIN and the + # installed qt lib directory. + local_rpath = '$ORIGIN/' + qt_lib_dir = self.qtinfo.libs_dir + if OPTION_STANDALONE: + qt_lib_dir = "$ORIGIN/Qt/lib" + final_rpath = local_rpath + ':' + qt_lib_dir + cmd = [patchelf_path, '--set-rpath', final_rpath, srcpath] + if run_process(cmd) != 0: + raise RuntimeError("Error patching rpath in " + srcpath) + + elif sys.platform == 'darwin': + pyside_libs = [lib for lib in os.listdir( + package_path) if filter_match(lib, ["*.so", "*.dylib"])] + def rpath_cmd(srcpath): + final_rpath = '' + # Command line rpath option takes precedence over + # automatically added one. + if OPTION_RPATH_VALUES: + final_rpath = OPTION_RPATH_VALUES + else: + if OPTION_STANDALONE: + final_rpath = "@loader_path/Qt/lib" + else: + final_rpath = self.qtinfo.libs_dir + macos_fix_rpaths_for_library(srcpath, final_rpath) + + else: + raise RuntimeError('Not configured for platform ' + + sys.platform) + + pyside_libs.extend(executables) + + # Update rpath in PySide2 libs + for srcname in pyside_libs: + srcpath = os.path.join(package_path, srcname) + if os.path.isdir(srcpath) or os.path.islink(srcpath): + continue + if not os.path.exists(srcpath): + continue + rpath_cmd(srcpath) + print("Patched rpath to '$ORIGIN/' (Linux) or " + "updated rpath (OS/X) in {}.".format(srcpath)) + + +try: + with open(os.path.join(setup_script_dir, 'README.rst')) as f: + README = f.read() + with open(os.path.join(setup_script_dir, 'CHANGES.rst')) as f: + CHANGES = f.read() +except IOError: + README = CHANGES = '' + + +cmd_class_dict = { + 'build': pyside_build, + 'build_py': pyside_build_py, + 'build_ext': pyside_build_ext, + 'bdist_egg': pyside_bdist_egg, + 'develop': pyside_develop, + 'install': pyside_install, + 'install_lib': pyside_install_lib +} +if wheel_module_exists: + cmd_class_dict['bdist_wheel'] = pyside_build_wheel diff --git a/build_scripts/options.py b/build_scripts/options.py new file mode 100644 index 000000000..e5b6ebd34 --- /dev/null +++ b/build_scripts/options.py @@ -0,0 +1,80 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python project. +## +## $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 + +from .utils import has_option, option_value + +# Declare options +OPTION_DEBUG = has_option("debug") +OPTION_RELWITHDEBINFO = has_option('relwithdebinfo') +OPTION_QMAKE = option_value("qmake") +OPTION_QT_VERSION = option_value("qt") +OPTION_CMAKE = option_value("cmake") +OPTION_OPENSSL = option_value("openssl") +OPTION_ONLYPACKAGE = has_option("only-package") +OPTION_STANDALONE = has_option("standalone") +OPTION_MAKESPEC = option_value("make-spec") +OPTION_IGNOREGIT = has_option("ignore-git") +# don't include pyside2-examples +OPTION_NOEXAMPLES = has_option("no-examples") +# number of parallel build jobs +OPTION_JOBS = option_value('jobs') +# Legacy, not used any more. +OPTION_JOM = has_option('jom') +# Do not use jom instead of nmake with msvc +OPTION_NO_JOM = has_option('no-jom') +OPTION_BUILDTESTS = has_option("build-tests") +OPTION_MACOS_ARCH = option_value("macos-arch") +OPTION_MACOS_USE_LIBCPP = has_option("macos-use-libc++") +OPTION_MACOS_SYSROOT = option_value("macos-sysroot") +OPTION_MACOS_DEPLOYMENT_TARGET = option_value("macos-deployment-target") +OPTION_XVFB = has_option("use-xvfb") +OPTION_REUSE_BUILD = has_option("reuse-build") +OPTION_SKIP_CMAKE = has_option("skip-cmake") +OPTION_SKIP_MAKE_INSTALL = has_option("skip-make-install") +OPTION_SKIP_PACKAGING = has_option("skip-packaging") +OPTION_MODULE_SUBSET = option_value("module-subset") +OPTION_RPATH_VALUES = option_value("rpath") +OPTION_QT_CONF_PREFIX = option_value("qt-conf-prefix") +OPTION_QT_SRC = option_value("qt-src-dir") +OPTION_VERBOSE_BUILD = has_option("verbose-build") +OPTION_SANITIZE_ADDRESS = has_option("sanitize-address") +OPTION_SNAPSHOT_BUILD = has_option("snapshot-build") + diff --git a/build_scripts/platforms/__init__.py b/build_scripts/platforms/__init__.py new file mode 100644 index 000000000..f1ebb00d9 --- /dev/null +++ b/build_scripts/platforms/__init__.py @@ -0,0 +1,38 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python project. +## +## $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$ +## +############################################################################# diff --git a/build_scripts/platforms/linux.py b/build_scripts/platforms/linux.py new file mode 100644 index 000000000..cb8d86ecf --- /dev/null +++ b/build_scripts/platforms/linux.py @@ -0,0 +1,111 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python project. +## +## $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 ..options import * +from ..utils import copydir, copyfile, copy_icu_libs, find_files_using_glob + +def prepare_standalone_package_linux(self, executables, vars): + built_modules = vars['built_modules'] + + # <qt>/lib/* -> <setup>/PySide2/Qt/lib + destination_lib_dir = "{pyside_package_dir}/PySide2/Qt/lib" + copydir("{qt_lib_dir}", destination_lib_dir, + filter=[ + "libQt5*.so.?", + "libicu*.so.??", + ], + recursive=False, vars=vars, force_copy_symlinks=True) + + # Check if ICU libraries were copied over to the destination + # Qt libdir. + resolved_destination_lib_dir = destination_lib_dir.format(**vars) + maybe_icu_libs = find_files_using_glob(resolved_destination_lib_dir, + "libicu*") + + # If no ICU libraries are present in the Qt libdir (like when + # Qt is built against system ICU, or in the Coin CI where ICU + # libs are in a different directory) try to find out / resolve + # which ICU libs are used by QtCore (if used at all) using a + # custom written ldd, and copy the ICU libs to the Pyside Qt + # dir if necessary. We choose the QtCore lib to inspect, by + # checking which QtCore library the shiboken2 executable uses. + if not maybe_icu_libs: + copy_icu_libs(resolved_destination_lib_dir) + + if self.is_webengine_built(built_modules): + copydir("{qt_lib_execs_dir}", + "{pyside_package_dir}/PySide2/Qt/libexec", + filter=None, + recursive=False, + vars=vars) + + copydir("{qt_prefix_dir}/resources", + "{pyside_package_dir}/PySide2/Qt/resources", + filter=None, + recursive=False, + vars=vars) + + # <qt>/plugins/* -> <setup>/PySide2/Qt/plugins + copydir("{qt_plugins_dir}", + "{pyside_package_dir}/PySide2/Qt/plugins", + filter=["*.so"], + recursive=True, + vars=vars) + + # <qt>/qml/* -> <setup>/PySide2/Qt/qml + copydir("{qt_qml_dir}", + "{pyside_package_dir}/PySide2/Qt/qml", + filter=None, + force=False, + recursive=True, + vars=vars) + + # <qt>/translations/* -> <setup>/PySide2/Qt/translations + + copydir("{qt_translations_dir}", + "{pyside_package_dir}/PySide2/Qt/translations", + filter=["*.qm", "*.pak"], + force=False, + vars=vars) + + # Copy the qt.conf file to libexec. + copyfile( + "{build_dir}/pyside2/PySide2/qt.conf", + "{pyside_package_dir}/PySide2/Qt/libexec", + vars=vars) diff --git a/build_scripts/platforms/macos.py b/build_scripts/platforms/macos.py new file mode 100644 index 000000000..4b0fe092a --- /dev/null +++ b/build_scripts/platforms/macos.py @@ -0,0 +1,156 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python project. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +import fnmatch, os +from ..utils import copydir, copyfile, macos_fix_rpaths_for_library + +def prepare_standalone_package_macos(self, executables, vars): + built_modules = vars['built_modules'] + + # Directory filter for skipping unnecessary files. + def general_dir_filter(dir_name, parent_full_path, dir_full_path): + if fnmatch.fnmatch(dir_name, "*.dSYM"): + return False + return True + + # <qt>/lib/* -> <setup>/PySide2/Qt/lib + if self.qt_is_framework_build(): + framework_built_modules = [ + 'Qt' + name + '.framework' for name in built_modules] + + def framework_dir_filter(dir_name, parent_full_path, + dir_full_path): + if '.framework' in dir_name: + if dir_name.startswith('QtWebEngine') and not \ + self.is_webengine_built(built_modules): + return False + if dir_name in ['Headers', 'fonts']: + return False + if dir_full_path.endswith('Versions/Current'): + return False + if dir_full_path.endswith('Versions/5/Resources'): + return False + if dir_full_path.endswith('Versions/5/Helpers'): + return False + return general_dir_filter(dir_name, parent_full_path, + dir_full_path) + + copydir("{qt_lib_dir}", "{pyside_package_dir}/PySide2/Qt/lib", + recursive=True, vars=vars, + ignore=["*.la", "*.a", "*.cmake", "*.pc", "*.prl"], + dir_filter_function=framework_dir_filter) + + # Fix rpath for WebEngine process executable. The already + # present rpath does not work because it assumes a symlink + # from Versions/5/Helpers, thus adding two more levels of + # directory hierarchy. + if self.is_webengine_built(built_modules): + qt_lib_path = "{pyside_package_dir}/PySide2/Qt/lib".format( + **vars) + bundle = "QtWebEngineCore.framework/Helpers/" + bundle += "QtWebEngineProcess.app" + binary = "Contents/MacOS/QtWebEngineProcess" + webengine_process_path = os.path.join(bundle, binary) + final_path = os.path.join(qt_lib_path, webengine_process_path) + rpath = "@loader_path/../../../../../" + macos_fix_rpaths_for_library(final_path, rpath) + else: + ignored_modules = [] + if not self.is_webengine_built(built_modules): + ignored_modules.extend(['libQt5WebEngine*.dylib']) + if 'WebKit' not in built_modules: + ignored_modules.extend(['libQt5WebKit*.dylib']) + accepted_modules = ['libQt5*.5.dylib'] + + copydir("{qt_lib_dir}", + "{pyside_package_dir}/PySide2/Qt/lib", + filter=accepted_modules, + ignore=ignored_modules, + recursive=True, vars=vars, force_copy_symlinks=True) + + if self.is_webengine_built(built_modules): + copydir("{qt_lib_execs_dir}", + "{pyside_package_dir}/PySide2/Qt/libexec", + filter=None, + recursive=False, + vars=vars) + + copydir("{qt_prefix_dir}/resources", + "{pyside_package_dir}/PySide2/Qt/resources", + filter=None, + recursive=False, + vars=vars) + + # Fix rpath for WebEngine process executable. + pyside_package_dir = vars['pyside_package_dir'] + qt_libexec_path = "{}/PySide2/Qt/libexec".format(pyside_package_dir) + binary = "QtWebEngineProcess" + final_path = os.path.join(qt_libexec_path, binary) + rpath = "@loader_path/../lib" + macos_fix_rpaths_for_library(final_path, rpath) + + # Copy the qt.conf file to libexec. + copyfile( + "{build_dir}/pyside2/PySide2/qt.conf", + "{pyside_package_dir}/PySide2/Qt/libexec", + vars=vars) + + # <qt>/plugins/* -> <setup>/PySide2/Qt/plugins + copydir("{qt_plugins_dir}", + "{pyside_package_dir}/PySide2/Qt/plugins", + filter=["*.dylib"], + recursive=True, + dir_filter_function=general_dir_filter, + vars=vars) + + # <qt>/qml/* -> <setup>/PySide2/Qt/qml + copydir("{qt_qml_dir}", + "{pyside_package_dir}/PySide2/Qt/qml", + filter=None, + recursive=True, + force=False, + dir_filter_function=general_dir_filter, + vars=vars) + + # <qt>/translations/* -> <setup>/PySide2/Qt/translations + copydir("{qt_translations_dir}", + "{pyside_package_dir}/PySide2/Qt/translations", + filter=["*.qm", "*.pak"], + force=False, + vars=vars) diff --git a/build_scripts/platforms/unix.py b/build_scripts/platforms/unix.py new file mode 100644 index 000000000..9370e0a22 --- /dev/null +++ b/build_scripts/platforms/unix.py @@ -0,0 +1,167 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python project. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +import os, re, sys +from .linux import prepare_standalone_package_linux +from .macos import prepare_standalone_package_macos +from ..options import * +from ..utils import copydir, copyfile, rmtree, makefile +from ..utils import regenerate_qt_resources + +def prepare_packages_posix(self, vars): + executables = [] + # <build>/shiboken2/doc/html/* -> + # <setup>/PySide2/docs/shiboken2 + copydir( + "{build_dir}/shiboken2/doc/html", + "{pyside_package_dir}/PySide2/docs/shiboken2", + force=False, vars=vars) + # <install>/lib/site-packages/PySide2/* -> <setup>/PySide2 + copydir( + "{site_packages_dir}/PySide2", + "{pyside_package_dir}/PySide2", + vars=vars) + # <install>/lib/site-packages/shiboken2.so -> + # <setup>/PySide2/shiboken2.so + shiboken_module_name = 'shiboken2.so' + shiboken_src_path = "{site_packages_dir}".format(**vars) + maybe_shiboken_names = [f for f in os.listdir(shiboken_src_path) + if re.match(r'shiboken.*\.so', f)] + if maybe_shiboken_names: + shiboken_module_name = maybe_shiboken_names[0] + vars.update({'shiboken_module_name': shiboken_module_name}) + copyfile( + "{site_packages_dir}/{shiboken_module_name}", + "{pyside_package_dir}/PySide2/{shiboken_module_name}", + vars=vars) + # <install>/lib/site-packages/pyside2uic/* -> + # <setup>/pyside2uic + copydir( + "{site_packages_dir}/pyside2uic", + "{pyside_package_dir}/pyside2uic", + force=False, vars=vars) + if sys.version_info[0] > 2: + rmtree("{pyside_package_dir}/pyside2uic/port_v2".format(**vars)) + else: + rmtree("{pyside_package_dir}/pyside2uic/port_v3".format(**vars)) + # <install>/bin/pyside2-uic -> PySide2/scripts/uic.py + makefile( + "{pyside_package_dir}/PySide2/scripts/__init__.py", + vars=vars) + copyfile( + "{install_dir}/bin/pyside2-uic", + "{pyside_package_dir}/PySide2/scripts/uic.py", + force=False, vars=vars) + # <install>/bin/* -> PySide2/ + executables.extend(copydir( + "{install_dir}/bin/", + "{pyside_package_dir}/PySide2", + filter=[ + "pyside2-lupdate", + "pyside2-rcc", + "shiboken2", + ], + recursive=False, vars=vars)) + # <install>/lib/lib* -> PySide2/ + config = self.get_built_pyside_config(vars) + def adjusted_lib_name(name, version): + postfix = '' + if sys.platform.startswith('linux'): + postfix = '.so.' + version + elif sys.platform == 'darwin': + postfix = '.' + version + '.dylib' + return name + postfix + copydir( + "{install_dir}/lib/", + "{pyside_package_dir}/PySide2", + filter=[ + adjusted_lib_name("libpyside*", + config['pyside_library_soversion']), + adjusted_lib_name("libshiboken*", + config['shiboken_library_soversion']), + ], + recursive=False, vars=vars, force_copy_symlinks=True) + # <install>/share/PySide2/typesystems/* -> + # <setup>/PySide2/typesystems + copydir( + "{install_dir}/share/PySide2/typesystems", + "{pyside_package_dir}/PySide2/typesystems", + vars=vars) + # <install>/include/* -> <setup>/PySide2/include + copydir( + "{install_dir}/include", + "{pyside_package_dir}/PySide2/include", + vars=vars) + # <source>/pyside2/PySide2/support/* -> + # <setup>/PySide2/support/* + copydir( + "{build_dir}/pyside2/PySide2/support", + "{pyside_package_dir}/PySide2/support", + vars=vars) + if not OPTION_NOEXAMPLES: + # examples/* -> <setup>/PySide2/examples + copydir(os.path.join(self.script_dir, "examples"), + "{pyside_package_dir}/PySide2/examples", + force=False, vars=vars) + # Re-generate examples Qt resource files for Python 3 + # compatibility + if sys.version_info[0] == 3: + examples_path = "{pyside_package_dir}/PySide2/examples".format( + **vars) + pyside_rcc_path = "{install_dir}/bin/pyside2-rcc".format( + **vars) + pyside_rcc_options = '-py3' + regenerate_qt_resources(examples_path, pyside_rcc_path, + pyside_rcc_options) + # Copy Qt libs to package + if OPTION_STANDALONE: + vars['built_modules'] = config['built_modules'] + if sys.platform == 'darwin': + prepare_standalone_package_macos(self, executables, vars) + else: + prepare_standalone_package_linux(self, executables, vars) + + # Copy over clang before rpath patching. + self.prepare_standalone_clang(is_win=False) + + # Update rpath to $ORIGIN + if (sys.platform.startswith('linux') or + sys.platform.startswith('darwin')): + self.update_rpath("{pyside_package_dir}/PySide2".format(**vars), + executables) diff --git a/build_scripts/platforms/windows_desktop.py b/build_scripts/platforms/windows_desktop.py new file mode 100644 index 000000000..79ca721ae --- /dev/null +++ b/build_scripts/platforms/windows_desktop.py @@ -0,0 +1,326 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python project. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +import functools +import os, re, sys +from ..options import * +from ..utils import copydir, copyfile, rmtree, makefile +from ..utils import regenerate_qt_resources, filter_match +def prepare_packages_win32(self, vars): + # For now, debug symbols will not be shipped into the package. + copy_pdbs = False + pdbs = [] + if (self.debug or self.build_type == 'RelWithDebInfo') and copy_pdbs: + pdbs = ['*.pdb'] + # <install>/lib/site-packages/PySide2/* -> <setup>/PySide2 + copydir( + "{site_packages_dir}/PySide2", + "{pyside_package_dir}/PySide2", + vars=vars) + built_modules = self.get_built_pyside_config(vars)['built_modules'] + + # <build>/pyside2/PySide2/*.pdb -> <setup>/PySide2 + copydir( + "{build_dir}/pyside2/PySide2", + "{pyside_package_dir}/PySide2", + filter=pdbs, + recursive=False, vars=vars) + + # <build>/shiboken2/doc/html/* -> + # <setup>/PySide2/docs/shiboken2 + copydir( + "{build_dir}/shiboken2/doc/html", + "{pyside_package_dir}/PySide2/docs/shiboken2", + force=False, vars=vars) + + # <install>/lib/site-packages/shiboken2.pyd -> + # <setup>/PySide2/shiboken2.pyd + shiboken_module_name = 'shiboken2.pyd' + shiboken_src_path = "{site_packages_dir}".format(**vars) + maybe_shiboken_names = [f for f in os.listdir(shiboken_src_path) + if re.match(r'shiboken.*\.pyd', f)] + if maybe_shiboken_names: + shiboken_module_name = maybe_shiboken_names[0] + vars.update({'shiboken_module_name': shiboken_module_name}) + copyfile( + "{site_packages_dir}/{shiboken_module_name}", + "{pyside_package_dir}/PySide2/{shiboken_module_name}", + vars=vars) + # @TODO: Fix this .pdb file not to overwrite release + # {shibokengenerator}.pdb file. + # Task-number: PYSIDE-615 + copydir( + "{build_dir}/shiboken2/shibokenmodule", + "{pyside_package_dir}/PySide2", + filter=pdbs, + recursive=False, vars=vars) + + # <install>/lib/site-packages/pyside2uic/* -> + # <setup>/pyside2uic + copydir( + "{site_packages_dir}/pyside2uic", + "{pyside_package_dir}/pyside2uic", + force=False, vars=vars) + if sys.version_info[0] > 2: + rmtree("{pyside_package_dir}/pyside2uic/port_v2".format(**vars)) + else: + rmtree("{pyside_package_dir}/pyside2uic/port_v3".format(**vars)) + + # <install>/bin/pyside2-uic -> PySide2/scripts/uic.py + makefile( + "{pyside_package_dir}/PySide2/scripts/__init__.py", + vars=vars) + copyfile( + "{install_dir}/bin/pyside2-uic", + "{pyside_package_dir}/PySide2/scripts/uic.py", + force=False, vars=vars) + + # <install>/bin/*.exe,*.dll,*.pdb -> PySide2/ + copydir( + "{install_dir}/bin/", + "{pyside_package_dir}/PySide2", + filter=["*.exe", "*.dll"], + recursive=False, vars=vars) + # @TODO: Fix this .pdb file not to overwrite release + # {shibokenmodule}.pdb file. + # Task-number: PYSIDE-615 + copydir( + "{build_dir}/shiboken2/generator", + "{pyside_package_dir}/PySide2", + filter=pdbs, + recursive=False, vars=vars) + + # <install>/lib/*.lib -> PySide2/ + copydir( + "{install_dir}/lib/", + "{pyside_package_dir}/PySide2", + filter=["*.lib"], + recursive=False, vars=vars) + + # <install>/share/PySide2/typesystems/* -> + # <setup>/PySide2/typesystems + copydir( + "{install_dir}/share/PySide2/typesystems", + "{pyside_package_dir}/PySide2/typesystems", + vars=vars) + + # <install>/include/* -> <setup>/PySide2/include + copydir( + "{install_dir}/include", + "{pyside_package_dir}/PySide2/include", + vars=vars) + + # <source>/pyside2/PySide2/support/* -> + # <setup>/PySide2/support/* + copydir( + "{build_dir}/pyside2/PySide2/support", + "{pyside_package_dir}/PySide2/support", + vars=vars) + + if not OPTION_NOEXAMPLES: + # examples/* -> <setup>/PySide2/examples + copydir(os.path.join(self.script_dir, "examples"), + "{pyside_package_dir}/PySide2/examples", + force=False, vars=vars) + # Re-generate examples Qt resource files for Python 3 + # compatibility + if sys.version_info[0] == 3: + examples_path = "{pyside_package_dir}/PySide2/examples".format( + **vars) + pyside_rcc_path = "{install_dir}/bin/pyside2-rcc".format( + **vars) + pyside_rcc_options = '-py3' + regenerate_qt_resources(examples_path, pyside_rcc_path, + pyside_rcc_options) + + # <ssl_libs>/* -> <setup>/PySide2/openssl + copydir("{ssl_libs_dir}", "{pyside_package_dir}/PySide2/openssl", + filter=[ + "libeay32.dll", + "ssleay32.dll"], + force=False, vars=vars) + + # <qt>/bin/*.dll and Qt *.exe -> <setup>/PySide2 + qt_artifacts_permanent = [ + "opengl*.dll", + "d3d*.dll", + "libEGL*.dll", + "libGLESv2*.dll", + "designer.exe", + "linguist.exe", + "lrelease.exe", + "lupdate.exe", + "lconvert.exe", + "qtdiag.exe" + ] + copydir("{qt_bin_dir}", "{pyside_package_dir}/PySide2", + filter=qt_artifacts_permanent, + recursive=False, vars=vars) + + # <qt>/bin/*.dll and Qt *.pdbs -> <setup>/PySide2 part two + # File filter to copy only debug or only release files. + qt_dll_patterns = ["Qt5*{}.dll", "lib*{}.dll"] + if copy_pdbs: + qt_dll_patterns += ["Qt5*{}.pdb", "lib*{}.pdb"] + def qt_build_config_filter(patterns, file_name, file_full_path): + release = [a.format('') for a in patterns] + debug = [a.format('d') for a in patterns] + + # If qt is not a debug_and_release build, that means there + # is only one set of shared libraries, so we can just copy + # them. + if self.qtinfo.build_type != 'debug_and_release': + if filter_match(file_name, release): + return True + return False + + # In debug_and_release case, choosing which files to copy + # is more difficult. We want to copy only the files that + # match the PySide2 build type. So if PySide2 is built in + # debug mode, we want to copy only Qt debug libraries + # (ending with "d.dll"). Or vice versa. The problem is that + # some libraries have "d" as the last character of the + # actual library name (for example Qt5Gamepad.dll and + # Qt5Gamepadd.dll). So we can't just match a pattern ending + # in "d". Instead we check if there exists a file with the + # same name plus an additional "d" at the end, and using + # that information we can judge if the currently processed + # file is a debug or release file. + + # e.g. ["Qt5Cored", ".dll"] + file_split = os.path.splitext(file_name) + file_base_name = file_split[0] + file_ext = file_split[1] + # e.g. "/home/work/qt/qtbase/bin" + file_path_dir_name = os.path.dirname(file_full_path) + # e.g. "Qt5Coredd" + maybe_debug_name = file_base_name + 'd' + if self.debug: + filter = debug + def predicate(path): return not os.path.exists(path) + else: + filter = release + def predicate(path): return os.path.exists(path) + # e.g. "/home/work/qt/qtbase/bin/Qt5Coredd.dll" + other_config_path = os.path.join(file_path_dir_name, + maybe_debug_name + file_ext) + + if (filter_match(file_name, filter) and + predicate(other_config_path)): + return True + return False + + qt_dll_filter = functools.partial(qt_build_config_filter, + qt_dll_patterns) + copydir("{qt_bin_dir}", "{pyside_package_dir}/PySide2", + file_filter_function=qt_dll_filter, + recursive=False, vars=vars) + + # <qt>/plugins/* -> <setup>/PySide2/plugins + plugin_dll_patterns = ["*{}.dll"] + pdb_pattern = "*{}.pdb" + if copy_pdbs: + plugin_dll_patterns += [pdb_pattern] + plugin_dll_filter = functools.partial(qt_build_config_filter, + plugin_dll_patterns) + copydir("{qt_plugins_dir}", "{pyside_package_dir}/PySide2/plugins", + file_filter_function=plugin_dll_filter, + vars=vars) + + # <qt>/translations/* -> <setup>/PySide2/translations + copydir("{qt_translations_dir}", + "{pyside_package_dir}/PySide2/translations", + filter=["*.qm", "*.pak"], + force=False, + vars=vars) + + # <qt>/qml/* -> <setup>/PySide2/qml + qml_dll_patterns = ["*{}.dll"] + qml_ignore_patterns = qml_dll_patterns + [pdb_pattern] + # Remove the "{}" from the patterns + qml_ignore = [a.format('') for a in qml_ignore_patterns] + if copy_pdbs: + qml_dll_patterns += [pdb_pattern] + qml_ignore = [a.format('') for a in qml_dll_patterns] + qml_dll_filter = functools.partial(qt_build_config_filter, + qml_dll_patterns) + copydir("{qt_qml_dir}", "{pyside_package_dir}/PySide2/qml", + ignore=qml_ignore, + force=False, + recursive=True, + vars=vars) + copydir("{qt_qml_dir}", "{pyside_package_dir}/PySide2/qml", + file_filter_function=qml_dll_filter, + force=False, + recursive=True, + vars=vars) + + if self.is_webengine_built(built_modules): + copydir("{qt_prefix_dir}/resources", + "{pyside_package_dir}/PySide2/resources", + filter=None, + recursive=False, + vars=vars) + + filter = 'QtWebEngineProcess{}.exe'.format( + 'd' if self.debug else '') + copydir("{qt_bin_dir}", + "{pyside_package_dir}/PySide2", + filter=[filter], + recursive=False, vars=vars) + + # Copy the qt.conf file to prefix dir. + copyfile( + "{build_dir}/pyside2/PySide2/qt.conf", + "{pyside_package_dir}/PySide2", + vars=vars) + + self.prepare_standalone_clang(is_win=True) + + # pdb files for libshiboken and libpyside + copydir( + "{build_dir}/shiboken2/libshiboken", + "{pyside_package_dir}/PySide2", + filter=pdbs, + recursive=False, vars=vars) + copydir( + "{build_dir}/pyside2/libpyside", + "{pyside_package_dir}/PySide2", + filter=pdbs, + recursive=False, vars=vars) diff --git a/build_scripts/qtinfo.py b/build_scripts/qtinfo.py new file mode 100644 index 000000000..0abed96d0 --- /dev/null +++ b/build_scripts/qtinfo.py @@ -0,0 +1,237 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python project. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +import os, sys, re, subprocess +from distutils.spawn import find_executable + +class QtInfo(object): + def __init__(self, qmake_command=None): + self.initialized = False + + if qmake_command: + self._qmake_command = qmake_command + else: + self._qmake_command = [find_executable("qmake"),] + + # Dict to cache qmake values. + self._query_dict = {} + # Dict to cache mkspecs variables. + self._mkspecs_dict = {} + # Initialize the properties. + self._initProperties() + + def getQMakeCommand(self): + qmake_command_string = self._qmake_command[0] + for entry in self._qmake_command[1:]: + qmake_command_string += " {}".format(entry) + return qmake_command_string + + def getVersion(self): + return self.getProperty("QT_VERSION") + + def getBinsPath(self): + return self.getProperty("QT_INSTALL_BINS") + + def getLibsPath(self): + return self.getProperty("QT_INSTALL_LIBS") + + def getLibsExecsPath(self): + return self.getProperty("QT_INSTALL_LIBEXECS") + + def getPluginsPath(self): + return self.getProperty("QT_INSTALL_PLUGINS") + + def getPrefixPath(self): + return self.getProperty("QT_INSTALL_PREFIX") + + def getImportsPath(self): + return self.getProperty("QT_INSTALL_IMPORTS") + + def getTranslationsPath(self): + return self.getProperty("QT_INSTALL_TRANSLATIONS") + + def getHeadersPath(self): + return self.getProperty("QT_INSTALL_HEADERS") + + def getDocsPath(self): + return self.getProperty("QT_INSTALL_DOCS") + + def getQmlPath(self): + return self.getProperty("QT_INSTALL_QML") + + def getMacOSMinDeploymentTarget(self): + """ Return value is a macOS version or None. """ + return self.getProperty("QMAKE_MACOSX_DEPLOYMENT_TARGET") + + def getBuildType(self): + """ + Return value is either debug, release, debug_release, or None. + """ + return self.getProperty("BUILD_TYPE") + + def getSrcDir(self): + """ Return path to Qt src dir or None.. """ + return self.getProperty("QT_INSTALL_PREFIX/src") + + def getProperty(self, prop_name): + if prop_name not in self._query_dict: + return None + return self._query_dict[prop_name] + + def getProperties(self): + return self._query_dict + + def getMkspecsVariables(self): + return self._mkspecs_dict + + def _getQMakeOutput(self, args_list = []): + cmd = self._qmake_command + args_list + proc = subprocess.Popen(cmd, stdout = subprocess.PIPE, shell=False) + output = proc.communicate()[0] + proc.wait() + if proc.returncode != 0: + return None + if sys.version_info >= (3,): + output = str(output, 'ascii').strip() + else: + output = output.strip() + return output + + def _parseQueryProperties(self, process_output): + props = {} + if not process_output: + return props + lines = [s.strip() for s in process_output.splitlines()] + for line in lines: + if line and ':' in line: + key, value = line.split(':', 1) + props[key] = value + return props + + def _getQueryProperties(self): + output = self._getQMakeOutput(['-query']) + self._query_dict = self._parseQueryProperties(output) + + def _parseQtBuildType(self): + key = 'QT_CONFIG' + if key not in self._mkspecs_dict: + return None + + qt_config = self._mkspecs_dict[key] + if 'debug_and_release' in qt_config: + return 'debug_and_release' + + split = qt_config.split(' ') + if 'release' in split and 'debug' in split: + return 'debug_and_release' + + if 'release' in split: + return 'release' + + if 'debug' in split: + return 'debug' + + return None + + def _getOtherProperties(self): + # Get the src property separately, because it is not returned by + # qmake unless explicitly specified. + key = 'QT_INSTALL_PREFIX/src' + result = self._getQMakeOutput(['-query', key]) + self._query_dict[key] = result + + # Get mkspecs variables and cache them. + self._getQMakeMkspecsVariables() + + # Get macOS minimum deployment target. + key = 'QMAKE_MACOSX_DEPLOYMENT_TARGET' + if key in self._mkspecs_dict: + self._query_dict[key] = self._mkspecs_dict[key] + + # Figure out how Qt was built: + # debug mode, release mode, or both. + build_type = self._parseQtBuildType() + if build_type: + self._query_dict['BUILD_TYPE'] = build_type + + def _initProperties(self): + self._getQueryProperties() + self._getOtherProperties() + + def _getQMakeMkspecsVariables(self): + # Create empty temporary qmake project file. + temp_file_name = 'qmake_fake_empty_project.txt' + open(temp_file_name, 'a').close() + + # Query qmake for all of its mkspecs variables. + qmakeOutput = self._getQMakeOutput(['-E', temp_file_name]) + lines = [s.strip() for s in qmakeOutput.splitlines()] + pattern = re.compile(r"^(.+?)=(.+?)$") + for line in lines: + found = pattern.search(line) + if found: + key = found.group(1).strip() + value = found.group(2).strip() + self._mkspecs_dict[key] = value + + # We need to clean up after qmake, which always creates a + # .qmake.stash file after a -E invocation. + qmake_stash_file = os.path.join(os.getcwd(), ".qmake.stash") + if os.path.exists(qmake_stash_file): + os.remove(qmake_stash_file) + + # Also clean up the temporary empty project file. + if os.path.exists(temp_file_name): + os.remove(temp_file_name) + + version = property(getVersion) + bins_dir = property(getBinsPath) + libs_dir = property(getLibsPath) + lib_execs_dir = property(getLibsExecsPath) + plugins_dir = property(getPluginsPath) + prefix_dir = property(getPrefixPath) + qmake_command = property(getQMakeCommand) + imports_dir = property(getImportsPath) + translations_dir = property(getTranslationsPath) + headers_dir = property(getHeadersPath) + docs_dir = property(getDocsPath) + qml_dir = property(getQmlPath) + macos_min_deployment_target = property(getMacOSMinDeploymentTarget) + build_type = property(getBuildType) + src_dir = property(getSrcDir) diff --git a/build_scripts/utils.py b/build_scripts/utils.py new file mode 100644 index 000000000..e69c9a58a --- /dev/null +++ b/build_scripts/utils.py @@ -0,0 +1,1075 @@ +############################################################################# +## +## Copyright (C) 2018 The Qt Company Ltd. +## Contact: https://www.qt.io/licensing/ +## +## This file is part of the Qt for Python project. +## +## $QT_BEGIN_LICENSE:LGPL$ +## Commercial License Usage +## Licensees holding valid commercial Qt licenses may use this file in +## accordance with the commercial license agreement provided with the +## Software or, alternatively, in accordance with the terms contained in +## a written agreement between you and The Qt Company. For licensing terms +## and conditions see https://www.qt.io/terms-conditions. For further +## information use the contact form at https://www.qt.io/contact-us. +## +## GNU Lesser General Public License Usage +## Alternatively, this file may be used under the terms of the GNU Lesser +## General Public License version 3 as published by the Free Software +## Foundation and appearing in the file LICENSE.LGPL3 included in the +## packaging of this file. Please review the following information to +## ensure the GNU Lesser General Public License version 3 requirements +## will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +## +## GNU General Public License Usage +## Alternatively, this file may be used under the terms of the GNU +## General Public License version 2.0 or (at your option) the GNU General +## Public license version 3 or any later version approved by the KDE Free +## Qt Foundation. The licenses are as published by the Free Software +## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +## included in the packaging of this file. Please review the following +## information to ensure the GNU General Public License requirements will +## be met: https://www.gnu.org/licenses/gpl-2.0.html and +## https://www.gnu.org/licenses/gpl-3.0.html. +## +## $QT_END_LICENSE$ +## +############################################################################# + +import sys +import os +import re +import stat +import errno +import time +import shutil +import subprocess +import fnmatch +import glob +import itertools +import popenasync +import glob + +# There is no urllib.request in Python2 +try: + import urllib.request as urllib +except ImportError: + import urllib + +from distutils import log +from distutils.errors import DistutilsOptionError +from distutils.errors import DistutilsSetupError +from distutils.spawn import spawn +from distutils.spawn import DistutilsExecError + +try: + WindowsError +except NameError: + WindowsError = None + + +def has_option(name): + try: + sys.argv.remove("--{}".format(name)) + return True + except ValueError: + pass + return False + + +def option_value(name): + for index, option in enumerate(sys.argv): + if option == '--' + name: + if index+1 >= len(sys.argv): + raise DistutilsOptionError("The option {} requires a " + "value".format(option)) + value = sys.argv[index+1] + sys.argv[index:index+2] = [] + return value + if option.startswith('--' + name + '='): + value = option[len(name)+3:] + sys.argv[index:index+1] = [] + return value + env_val = os.getenv(name.upper().replace('-', '_')) + return env_val + + +def filter_match(name, patterns): + for pattern in patterns: + if pattern is None: + continue + if fnmatch.fnmatch(name, pattern): + return True + return False + + +def update_env_path(newpaths): + paths = os.environ['PATH'].lower().split(os.pathsep) + for path in newpaths: + if not path.lower() in paths: + log.info("Inserting path '{}' to environment".format(path)) + paths.insert(0, path) + os.environ['PATH'] = path + os.pathsep + os.environ['PATH'] + + +def winsdk_setenv(platform_arch, build_type): + from distutils.msvc9compiler import VERSION as MSVC_VERSION + from distutils.msvc9compiler import Reg + from distutils.msvc9compiler import HKEYS + from distutils.msvc9compiler import WINSDK_BASE + + sdk_version_map = { + "v6.0a": 9.0, + "v6.1": 9.0, + "v7.0": 9.0, + "v7.0a": 10.0, + "v7.1": 10.0 + } + + log.info("Searching Windows SDK with MSVC compiler version {}".format( + MSVC_VERSION)) + setenv_paths = [] + for base in HKEYS: + sdk_versions = Reg.read_keys(base, WINSDK_BASE) + if sdk_versions: + for sdk_version in sdk_versions: + installationfolder = Reg.get_value(WINSDK_BASE + "\\" + + sdk_version, "installationfolder") + productversion = Reg.get_value(WINSDK_BASE + "\\" + + sdk_version, "productversion") + setenv_path = os.path.join(installationfolder, os.path.join( + 'bin', 'SetEnv.cmd')) + if not os.path.exists(setenv_path): + continue + if not sdk_version in sdk_version_map: + continue + if sdk_version_map[sdk_version] != MSVC_VERSION: + continue + setenv_paths.append(setenv_path) + if len(setenv_paths) == 0: + raise DistutilsSetupError( + "Failed to find the Windows SDK with MSVC compiler " + "version {}".format(MSVC_VERSION)) + for setenv_path in setenv_paths: + log.info("Found {}".format(setenv_path)) + + # Get SDK env (use latest SDK version installed on system) + setenv_path = setenv_paths[-1] + log.info("Using {} ".format(setenv_path)) + build_arch = "/x86" if platform_arch.startswith("32") else "/x64" + build_type = "/Debug" if build_type.lower() == "debug" else "/Release" + setenv_cmd = [setenv_path, build_arch, build_type] + setenv_env = get_environment_from_batch_command(setenv_cmd) + setenv_env_paths = os.pathsep.join([setenv_env[k] for k in setenv_env if k.upper() == 'PATH']).split(os.pathsep) + setenv_env_without_paths = dict([(k, setenv_env[k]) for k in setenv_env if k.upper() != 'PATH']) + + # Extend os.environ with SDK env + log.info("Initializing Windows SDK env...") + update_env_path(setenv_env_paths) + for k in sorted(setenv_env_without_paths): + v = setenv_env_without_paths[k] + log.info("Inserting '{} = {}' to environment".format(k, v)) + os.environ[k] = v + log.info("Done initializing Windows SDK env") + + +def find_vcdir(version): + """ + This is the customized version of + distutils.msvc9compiler.find_vcvarsall method + """ + from distutils.msvc9compiler import VS_BASE + from distutils.msvc9compiler import Reg + from distutils import log + vsbase = VS_BASE % version + try: + productdir = Reg.get_value(r"{}\Setup\VC".format(vsbase), "productdir") + except KeyError: + productdir = None + + # trying Express edition + if productdir is None: + try: + from distutils.msvc9compiler import VSEXPRESS_BASE + except ImportError: + pass + else: + vsbase = VSEXPRESS_BASE % version + try: + productdir = Reg.get_value(r"{}\Setup\VC".format(vsbase), + "productdir") + except KeyError: + productdir = None + log.debug("Unable to find productdir in registry") + + if not productdir or not os.path.isdir(productdir): + toolskey = "VS%0.f0COMNTOOLS" % version + toolsdir = os.environ.get(toolskey, None) + + if toolsdir and os.path.isdir(toolsdir): + productdir = os.path.join(toolsdir, os.pardir, os.pardir, "VC") + productdir = os.path.abspath(productdir) + if not os.path.isdir(productdir): + log.debug("{} is not a valid directory".format(productdir)) + return None + else: + log.debug("Env var {} is not set or invalid".format(toolskey)) + if not productdir: + log.debug("No productdir found") + return None + return productdir + + +def init_msvc_env(platform_arch, build_type): + from distutils.msvc9compiler import VERSION as MSVC_VERSION + + log.info("Searching MSVC compiler version {}".format(MSVC_VERSION)) + vcdir_path = find_vcdir(MSVC_VERSION) + if not vcdir_path: + raise DistutilsSetupError( + "Failed to find the MSVC compiler version {} on your " + "system.".formar(MSVC_VERSION)) + else: + log.info("Found {}".format(vcdir_path)) + + log.info("Searching MSVC compiler {} environment init script".format( + MSVC_VERSION)) + if platform_arch.startswith("32"): + vcvars_path = os.path.join(vcdir_path, "bin", "vcvars32.bat") + else: + vcvars_path = os.path.join(vcdir_path, "bin", "vcvars64.bat") + if not os.path.exists(vcvars_path): + vcvars_path = os.path.join(vcdir_path, "bin", "amd64", + "vcvars64.bat") + if not os.path.exists(vcvars_path): + vcvars_path = os.path.join(vcdir_path, "bin", "amd64", + "vcvarsamd64.bat") + + if not os.path.exists(vcvars_path): + # MSVC init script not found, try to find and init Windows SDK env + log.error("Failed to find the MSVC compiler environment init script " + "(vcvars.bat) on your system.") + winsdk_setenv(platform_arch, build_type) + return + else: + log.info("Found {}".format(vcvars_path)) + + # Get MSVC env + log.info("Using MSVC {} in {}".format(MSVC_VERSION, vcvars_path)) + msvc_arch = "x86" if platform_arch.startswith("32") else "amd64" + log.info("Getting MSVC env for {} architecture".format(msvc_arch)) + vcvars_cmd = [vcvars_path, msvc_arch] + msvc_env = get_environment_from_batch_command(vcvars_cmd) + msvc_env_paths = os.pathsep.join([msvc_env[k] for k in msvc_env if k.upper() == 'PATH']).split(os.pathsep) + msvc_env_without_paths = dict([(k, msvc_env[k]) for k in msvc_env if k.upper() != 'PATH']) + + # Extend os.environ with MSVC env + log.info("Initializing MSVC env...") + update_env_path(msvc_env_paths) + for k in sorted(msvc_env_without_paths): + v = msvc_env_without_paths[k] + log.info("Inserting '{} = {}' to environment".format(k, v)) + os.environ[k] = v + log.info("Done initializing MSVC env") + + +def copyfile(src, dst, force=True, vars=None, force_copy_symlink=False): + if vars is not None: + src = src.format(**vars) + dst = dst.format(**vars) + + if not os.path.exists(src) and not force: + log.info("**Skiping copy file {} to {}. " + "Source does not exists.".format(src, dst)) + return + + if not os.path.islink(src) or force_copy_symlink: + log.info("Copying file {} to {}.".format(src, dst)) + shutil.copy2(src, dst) + else: + linkTargetPath = os.path.realpath(src) + if os.path.dirname(linkTargetPath) == os.path.dirname(src): + linkTarget = os.path.basename(linkTargetPath) + linkName = os.path.basename(src) + currentDirectory = os.getcwd() + try: + targetDir = dst if os.path.isdir(dst) else os.path.dirname(dst) + os.chdir(targetDir) + if os.path.exists(linkName): + os.remove(linkName) + log.info("Symlinking {} -> {} in {}.".format(linkName, + linkTarget, targetDir)) + os.symlink(linkTarget, linkName) + except OSError: + log.error("{} -> {}: Error creating symlink".format(linkName, + linkTarget)) + finally: + os.chdir(currentDirectory) + else: + log.error("{} -> {}: Can only create symlinks within the same " + "directory".format(src, linkTargetPath)) + + return dst + + +def makefile(dst, content=None, vars=None): + if vars is not None: + if content is not None: + content = content.format(**vars) + dst = dst.format(**vars) + + log.info("Making file {}.".format(dst)) + + dstdir = os.path.dirname(dst) + if not os.path.exists(dstdir): + os.makedirs(dstdir) + + f = open(dst, "wt") + if content is not None: + f.write(content) + f.close() + + +def copydir(src, dst, filter=None, ignore=None, force=True, recursive=True, + vars=None, dir_filter_function=None, file_filter_function=None, + force_copy_symlinks=False): + + if vars is not None: + src = src.format(**vars) + dst = dst.format(**vars) + if filter is not None: + for i in range(len(filter)): + filter[i] = filter[i].format(**vars) + if ignore is not None: + for i in range(len(ignore)): + ignore[i] = ignore[i].format(**vars) + + if not os.path.exists(src) and not force: + log.info("**Skiping copy tree {} to {}. Source does not exists. " + "filter={}. ignore={}.".format(src, dst, filter, ignore)) + return [] + + log.info("Copying tree {} to {}. filter={}. ignore={}.".format(src, dst, + filter, ignore)) + + names = os.listdir(src) + + results = [] + errors = [] + for name in names: + srcname = os.path.join(src, name) + dstname = os.path.join(dst, name) + try: + if os.path.isdir(srcname): + if (dir_filter_function and + not dir_filter_function(name, src, srcname)): + continue + if recursive: + results.extend( + copydir(srcname, dstname, filter, ignore, force, + recursive, vars, dir_filter_function, + file_filter_function, force_copy_symlinks)) + else: + if ((file_filter_function is not None and + not file_filter_function(name, srcname)) or + (filter is not None and + not filter_match(name, filter)) or + (ignore is not None and filter_match(name, ignore))): + continue + if not os.path.exists(dst): + os.makedirs(dst) + results.append(copyfile(srcname, dstname, True, vars, + force_copy_symlinks)) + # catch the Error from the recursive copytree so that we can + # continue with other files + except shutil.Error as err: + errors.extend(err.args[0]) + except EnvironmentError as why: + errors.append((srcname, dstname, str(why))) + try: + if os.path.exists(dst): + shutil.copystat(src, dst) + except OSError as why: + if WindowsError is not None and isinstance(why, WindowsError): + # Copying file access times may fail on Windows + pass + else: + errors.extend((src, dst, str(why))) + if errors: + raise EnvironmentError(errors) + return results + + +def rmtree(dirname): + def handleRemoveReadonly(func, path, exc): + excvalue = exc[1] + if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES: + os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777 + func(path) + else: + raise + shutil.rmtree(dirname, ignore_errors=False, onerror=handleRemoveReadonly) + +def run_process_output(args, initial_env=None): + if initial_env is None: + initial_env = os.environ + stdOut = subprocess.Popen(args, env = initial_env, universal_newlines = 1, + stdout=subprocess.PIPE).stdout + result = [] + for rawLine in stdOut.readlines(): + line = rawLine if sys.version_info >= (3,) else rawLine.decode('utf-8') + result.append(line.rstrip()) + return result + +def run_process(args, initial_env=None): + def _log(buffer, checkNewLine=False): + endsWithNewLine = False + if buffer.endswith('\n'): + endsWithNewLine = True + if checkNewLine and buffer.find('\n') == -1: + return buffer + lines = buffer.splitlines() + buffer = '' + if checkNewLine and not endsWithNewLine: + buffer = lines[-1] + lines = lines[:-1] + for line in lines: + log.info(line.rstrip('\r')) + return buffer + _log("Running process in {0}: {1}".format(os.getcwd(), + " ".join([(" " in x and '"{0}"'.format(x) or x) for x in args]))) + + if sys.platform != "win32": + try: + spawn(args) + return 0 + except DistutilsExecError: + return -1 + + shell = False + if sys.platform == "win32": + shell = True + + if initial_env is None: + initial_env = os.environ + + proc = popenasync.Popen(args, + stdin = subprocess.PIPE, + stdout = subprocess.PIPE, + stderr = subprocess.STDOUT, + universal_newlines = 1, + shell = shell, + env = initial_env) + + log_buffer = None; + while proc.poll() is None: + log_buffer = _log(proc.read_async(wait=0.1, e=0)) + if log_buffer: + _log(log_buffer) + + proc.wait() + return proc.returncode + + +def get_environment_from_batch_command(env_cmd, initial=None): + """ + Take a command (either a single command or list of arguments) + and return the environment created after running that command. + Note that if the command must be a batch file or .cmd file, or the + changes to the environment will not be captured. + + If initial is supplied, it is used as the initial environment passed + to the child process. + """ + + def validate_pair(ob): + try: + if not (len(ob) == 2): + print("Unexpected result: {}".format(ob)) + raise ValueError + except: + return False + return True + + def consume(iter): + try: + while True: next(iter) + except StopIteration: + pass + + if not isinstance(env_cmd, (list, tuple)): + env_cmd = [env_cmd] + # construct the command that will alter the environment + env_cmd = subprocess.list2cmdline(env_cmd) + # create a tag so we can tell in the output when the proc is done + tag = 'Done running command' + # construct a cmd.exe command to do accomplish this + cmd = 'cmd.exe /E:ON /V:ON /s /c "{} && echo "{}" && set"'.format(env_cmd, + tag) + # launch the process + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, env=initial) + # parse the output sent to stdout + lines = proc.stdout + if sys.version_info[0] > 2: + # make sure the lines are strings + make_str = lambda s: s.decode() + lines = map(make_str, lines) + # consume whatever output occurs until the tag is reached + consume(itertools.takewhile(lambda l: tag not in l, lines)) + # define a way to handle each KEY=VALUE line + handle_line = lambda l: l.rstrip().split('=',1) + # parse key/values into pairs + pairs = map(handle_line, lines) + # make sure the pairs are valid + valid_pairs = filter(validate_pair, pairs) + # construct a dictionary of the pairs + result = dict(valid_pairs) + # let the process finish + proc.communicate() + return result + + +def regenerate_qt_resources(src, pyside_rcc_path, pyside_rcc_options): + names = os.listdir(src) + for name in names: + srcname = os.path.join(src, name) + if os.path.isdir(srcname): + regenerate_qt_resources(srcname, + pyside_rcc_path, + pyside_rcc_options) + elif srcname.endswith('.qrc'): + # Replace last occurence of '.qrc' in srcname + srcname_split = srcname.rsplit('.qrc', 1) + dstname = '_rc.py'.join(srcname_split) + if os.path.exists(dstname): + log.info('Regenerating {} from {}'.format(dstname, + os.path.basename(srcname))) + run_process([pyside_rcc_path, + pyside_rcc_options, + srcname, '-o', dstname]) + + +def back_tick(cmd, ret_err=False): + """ + Run command `cmd`, return stdout, or stdout, stderr, + return_code if `ret_err` is True. + + Roughly equivalent to ``check_output`` in Python 2.7 + + Parameters + ---------- + cmd : str + command to execute + ret_err : bool, optional + If True, return stderr and return_code in addition to stdout. + If False, just return stdout + + Returns + ------- + out : str or tuple + If `ret_err` is False, return stripped string containing stdout from + `cmd`. + If `ret_err` is True, return tuple of (stdout, stderr, return_code) + where ``stdout`` is the stripped stdout, and ``stderr`` is the stripped + stderr, and ``return_code`` is the process exit code. + + Raises + ------ + Raises RuntimeError if command returns non-zero exit code when ret_err + isn't set. + """ + proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE, shell=True) + out, err = proc.communicate() + if not isinstance(out, str): + # python 3 + out = out.decode() + err = err.decode() + retcode = proc.returncode + if retcode is None and not ret_err: + proc.terminate() + raise RuntimeError(cmd + ' process did not terminate') + if retcode != 0 and not ret_err: + raise RuntimeError("{} process returned code {}\n*** {}".format( + (cmd, retcode, err))) + out = out.strip() + if not ret_err: + return out + return out, err.strip(), retcode + + +MACOS_OUTNAME_RE = re.compile(r'\(compatibility version [\d.]+, current version ' + '[\d.]+\)') + +def macos_get_install_names(libpath): + """ + Get macOS library install names from library `libpath` using ``otool`` + + Parameters + ---------- + libpath : str + path to library + + Returns + ------- + install_names : list of str + install names in library `libpath` + """ + out = back_tick('otool -L ' + libpath) + libs = [line for line in out.split('\n')][1:] + return [MACOS_OUTNAME_RE.sub('', lib).strip() for lib in libs] + + +MACOS_RPATH_RE = re.compile(r"path (.+) \(offset \d+\)") + +def macos_get_rpaths(libpath): + """ Get rpath load commands from library `libpath` using ``otool`` + + Parameters + ---------- + libpath : str + path to library + + Returns + ------- + rpaths : list of str + rpath values stored in ``libpath`` + + Notes + ----- + See ``man dyld`` for more information on rpaths in libraries + """ + lines = back_tick('otool -l ' + libpath).split('\n') + ctr = 0 + rpaths = [] + while ctr < len(lines): + line = lines[ctr].strip() + if line != 'cmd LC_RPATH': + ctr += 1 + continue + assert lines[ctr + 1].strip().startswith('cmdsize') + rpath_line = lines[ctr + 2].strip() + match = MACOS_RPATH_RE.match(rpath_line) + if match is None: + raise RuntimeError('Unexpected path line: ' + rpath_line) + rpaths.append(match.groups()[0]) + ctr += 3 + return rpaths + + +def macos_fix_rpaths_for_library(library_path, qt_lib_dir): + """ Adds required rpath load commands to given library. + + This is a necessary post-installation step, to allow loading PySide + modules without setting DYLD_LIBRARY_PATH or DYLD_FRAMEWORK_PATH. + The CMake rpath commands which are added at build time are used only + for testing (make check), and they are stripped once the equivalent + of make install is executed (except for shiboken, which currently + uses CMAKE_INSTALL_RPATH_USE_LINK_PATH, which might be necessary to + remove in the future). + + Parameters + ---------- + library_path : str + path to library for which to set rpaths. + qt_lib_dir : str + rpath to installed Qt lib directory. + """ + + install_names = macos_get_install_names(library_path) + existing_rpath_commands = macos_get_rpaths(library_path) + + needs_loader_path = False + for install_name in install_names: + # Absolute path, skip it. + if install_name[0] == '/': + continue + + # If there are dynamic library install names that contain + # @rpath tokens, we will provide an rpath load command with the + # value of "@loader_path". This will allow loading dependent + # libraries from within the same directory as 'library_path'. + if install_name[0] == '@': + needs_loader_path = True + break + + if needs_loader_path and "@loader_path" not in existing_rpath_commands: + back_tick('install_name_tool -add_rpath {rpath} {library_path}'.format( + rpath="@loader_path", library_path=library_path)) + + # If the library depends on a Qt library, add an rpath load comment + # pointing to the Qt lib directory. + macos_add_qt_rpath(library_path, qt_lib_dir, existing_rpath_commands, + install_names) + +def macos_add_qt_rpath(library_path, qt_lib_dir, + existing_rpath_commands = [], library_dependencies = []): + """ + Adds an rpath load command to the Qt lib directory if necessary + + Checks if library pointed to by 'library_path' has Qt dependencies, + and adds an rpath load command that points to the Qt lib directory + (qt_lib_dir). + """ + if not existing_rpath_commands: + existing_rpath_commands = macos_get_rpaths(library_path) + + # Return early if qt rpath is already present. + if qt_lib_dir in existing_rpath_commands: + return + + # Check if any library dependencies are Qt libraries (hacky). + if not library_dependencies: + library_dependencies = macos_get_install_names(library_path) + + needs_qt_rpath = False + for library in library_dependencies: + if 'Qt' in library: + needs_qt_rpath = True + break + + if needs_qt_rpath: + back_tick('install_name_tool -add_rpath {rpath} {library_path}'.format( + rpath=qt_lib_dir, library_path=library_path)) + +# Find an executable specified by a glob pattern ('foo*') in the OS path +def findGlobInPath(pattern): + result = [] + if sys.platform == 'win32': + pattern += '.exe' + + for path in os.environ.get('PATH', '').split(os.pathsep): + for match in glob.glob(os.path.join(path, pattern)): + result.append(match) + return result + +# Locate the most recent version of llvmConfig in the path. +def findLlvmConfig(): + versionRe = re.compile('(\d+)\.(\d+)\.(\d+)') + result = None + lastVersionString = '000000' + for llvmConfig in findGlobInPath('llvm-config*'): + try: + output = run_process_output([llvmConfig, '--version']) + if output: + match = versionRe.match(output[0]) + if match: + versionString = '%02d%02d%02d' % (int(match.group(1)), + int(match.group(2)), int(match.group(3))) + if (versionString > lastVersionString): + result = llvmConfig + lastVersionString = versionString + except OSError: + pass + return result + +# Add Clang to path for Windows for the shiboken ApiExtractor tests. +# Revisit once Clang is bundled with Qt. +def detectClang(): + source = 'LLVM_INSTALL_DIR' + clangDir = os.environ.get(source, None) + if not clangDir: + source = 'CLANG_INSTALL_DIR' + clangDir = os.environ.get(source, None) + if not clangDir: + source = findLlvmConfig() + try: + if source is not None: + output = run_process_output([source, '--prefix']) + if output: + clangDir = output[0] + except OSError: + pass + if clangDir: + arch = '64' if sys.maxsize > 2**31-1 else '32' + clangDir = clangDir.replace('_ARCH_', arch) + return (clangDir, source) + +def download_and_extract_7z(fileurl, target): + """ Downloads 7z file from fileurl and extract to target """ + print("Downloading fileUrl {} ".format(fileurl)) + info = "" + try: + localfile, info = urllib.urlretrieve(fileurl) + except: + print("Error downloading {} : {}".format(fileurl, info)) + raise RuntimeError(' Error downloading {}'.format(fileurl)) + + try: + outputDir = "-o" + target + print("calling 7z x {} {}".format(localfile, outputDir)) + subprocess.call(["7z", "x", "-y", localfile, outputDir]) + except: + raise RuntimeError(' Error extracting {}'.format(localfile)) + +def split_and_strip(input): + lines = [s.strip() for s in input.splitlines()] + return lines + +def ldd_get_dependencies(executable_path): + """ + Returns a dictionary of dependencies that `executable_path` + depends on. + + The keys are library names and the values are the library paths. + + """ + output = ldd(executable_path) + lines = split_and_strip(output) + pattern = re.compile(r"\s*(.*?)\s+=>\s+(.*?)\s+\(.*\)") + dependencies = {} + for line in lines: + match = pattern.search(line) + if match: + dependencies[match.group(1)] = match.group(2) + return dependencies + +def ldd_get_paths_for_dependencies(dependencies_regex, executable_path = None, + dependencies = None): + """ + Returns file paths to shared library dependencies that match given + `dependencies_regex` against given `executable_path`. + + The function retrieves the list of shared library dependencies using + ld.so for the given `executable_path` in order to search for + libraries that match the `dependencies_regex`, and then returns a + list of absolute paths of the matching libraries. + + If no matching library is found in the list of dependencies, + an empty list is returned. + """ + + if not dependencies and not executable_path: + return None + + if not dependencies: + dependencies = ldd_get_dependencies(executable_path) + + pattern = re.compile(dependencies_regex) + + paths = [] + for key in dependencies: + match = pattern.search(key) + if match: + paths.append(dependencies[key]) + + return paths + +def ldd(executable_path): + """ + Returns ld.so output of shared library dependencies for given + `executable_path`. + + This is a partial port of /usr/bin/ldd from bash to Python. + The dependency list is retrieved by setting the + LD_TRACE_LOADED_OBJECTS=1 environment variable, and executing the + given path via the dynamic loader ld.so. + + Only works on Linux. The port is required to make this work on + systems that might not have ldd. + This is because ldd (on Ubuntu) is shipped in the libc-bin package + that, which might have a + minuscule percentage of not being installed. + + Parameters + ---------- + executable_path : str + path to executable or shared library. + + Returns + ------- + output : str + the raw output retrieved from the dynamic linker. + """ + + chosen_rtld = None + # List of ld's considered by ldd on Ubuntu (here's hoping it's the + # same on all distros). + rtld_list = ["/lib/ld-linux.so.2", "/lib64/ld-linux-x86-64.so.2", + "/libx32/ld-linux-x32.so.2"] + + # Choose appropriate runtime dynamic linker. + for rtld in rtld_list: + if os.path.isfile(rtld) and os.access(rtld, os.X_OK): + (_, _, code) = back_tick(rtld, True) + # Code 127 is returned by ld.so when called without any + # arguments (some kind of sanity check I guess). + if code == 127: + (_, _, code) = back_tick("{} --verify {}".format(rtld, + executable_path), True) + # Codes 0 and 2 mean given executable_path can be + # understood by ld.so. + if code in [0, 2]: + chosen_rtld = rtld + break + + if not chosen_rtld: + raise RuntimeError("Could not find appropriate ld.so to query " + "for dependencies.") + + # Query for shared library dependencies. + rtld_env = "LD_TRACE_LOADED_OBJECTS=1" + rtld_cmd = "{} {} {}".format(rtld_env, chosen_rtld, executable_path) + (out, _, return_code) = back_tick(rtld_cmd, True) + if return_code == 0: + return out + else: + raise RuntimeError("ld.so failed to query for dependent shared " + "libraries of {} ".format(executable_path)) + +def find_files_using_glob(path, pattern): + """ Returns list of files that matched glob `pattern` in `path`. """ + final_pattern = os.path.join(path, pattern) + maybe_files = glob.glob(final_pattern) + return maybe_files + +def find_qt_core_library_glob(lib_dir): + """ Returns path to the QtCore library found in `lib_dir`. """ + maybe_file = find_files_using_glob(lib_dir, "libQt5Core.so.?") + if len(maybe_file) == 1: + return maybe_file[0] + return None + +# @TODO: Possibly fix ICU library copying on macOS and Windows. +# This would require to implement the equivalent of the custom written +# ldd for the specified platforms. +# This has less priority because ICU libs are not used in the default +# Qt configuration build. +def copy_icu_libs(destination_lib_dir): + """ + Copy ICU libraries that QtCore depends on, + to given `destination_lib_dir`. + """ + qt_core_library_path = find_qt_core_library_glob(destination_lib_dir) + + if not qt_core_library_path or not os.path.exists(qt_core_library_path): + raise RuntimeError('QtCore library does not exist at path: {}. ' + 'Failed to copy ICU libraries.'.format(qt_core_library_path)) + + dependencies = ldd_get_dependencies(qt_core_library_path) + + icu_regex = r"^libicu.+" + icu_compiled_pattern = re.compile(icu_regex) + icu_required = False + for dependency in dependencies: + match = icu_compiled_pattern.search(dependency) + if match: + icu_required = True + break + + if icu_required: + paths = ldd_get_paths_for_dependencies(icu_regex, + dependencies=dependencies) + if not paths: + raise RuntimeError("Failed to find the necessary ICU libraries " + "required by QtCore.") + log.info('Copying the detected ICU libraries required by QtCore.') + + if not os.path.exists(destination_lib_dir): + os.makedirs(destination_lib_dir) + + for path in paths: + basename = os.path.basename(path) + destination = os.path.join(destination_lib_dir, basename) + copyfile(path, destination, force_copy_symlink=True) + # Patch the ICU libraries to contain the $ORIGIN rpath + # value, so that only the local package libraries are used. + linuxSetRPaths(destination, '$ORIGIN') + + # Patch the QtCore library to find the copied over ICU libraries + # (if necessary). + log.info("Checking if QtCore library needs a new rpath to make it " + "work with ICU libs.") + rpaths = linuxGetRPaths(qt_core_library_path) + if not rpaths or not rpathsHasOrigin(rpaths): + log.info('Patching QtCore library to contain $ORIGIN rpath.') + rpaths.insert(0, '$ORIGIN') + new_rpaths_string = ":".join(rpaths) + linuxSetRPaths(qt_core_library_path, new_rpaths_string) + +def linuxSetRPaths(executable_path, rpath_string): + """ Patches the `executable_path` with a new rpath string. """ + + if not hasattr(linuxSetRPaths, "patchelf_path"): + script_dir = os.getcwd() + patchelf_path = os.path.join(script_dir, "patchelf") + setattr(linuxSetRPaths, "patchelf_path", patchelf_path) + + cmd = [linuxSetRPaths.patchelf_path, '--set-rpath', + rpath_string, executable_path] + + if run_process(cmd) != 0: + raise RuntimeError("Error patching rpath in {}".format( + executable_path)) + +def linuxGetRPaths(executable_path): + """ + Returns a list of run path values embedded in the executable or just + an empty list. + """ + + cmd = "readelf -d {}".format(executable_path) + (out, err, code) = back_tick(cmd, True) + if code != 0: + raise RuntimeError("Running `readelf -d {}` failed with error " + "output:\n {}. ".format(executable_path, err)) + lines = split_and_strip(out) + pattern = re.compile(r"^.+?\(RUNPATH\).+?\[(.+?)\]$") + + rpath_line = None + for line in lines: + match = pattern.search(line) + if match: + rpath_line = match.group(1) + break + + rpaths = [] + + if rpath_line: + rpaths = rpath_line.split(':') + + return rpaths + +def rpathsHasOrigin(rpaths): + """ + Return True if the specified list of rpaths has an "$ORIGIN" value + (aka current dir). + """ + if not rpaths: + return False + + pattern = re.compile(r"^\$ORIGIN(/)?$") + for rpath in rpaths: + match = pattern.search(rpath) + if match: + return True + return False + +def memoize(function): + """ + Decorator to wrap a function with a memoizing callable. + It returns cached values when the wrapped function is called with + the same arguments. + """ + memo = {} + def wrapper(*args): + if args in memo: + return memo[args] + else: + rv = function(*args) + memo[args] = rv + return rv + return wrapper + +def get_python_dict(python_script_path): + try: + with open(python_script_path) as f: + python_dict = {} + code = compile(f.read(), python_script_path, 'exec') + exec(code, {}, python_dict) + return python_dict + except IOError as e: + print("get_python_dict: Couldn't get dict from python " + "file: {}.".format(python_script_path)) + raise |