aboutsummaryrefslogtreecommitdiffstats
path: root/build_scripts
diff options
context:
space:
mode:
Diffstat (limited to 'build_scripts')
-rw-r--r--build_scripts/__init__.py38
-rw-r--r--build_scripts/main.py1300
-rw-r--r--build_scripts/options.py80
-rw-r--r--build_scripts/platforms/__init__.py38
-rw-r--r--build_scripts/platforms/linux.py111
-rw-r--r--build_scripts/platforms/macos.py156
-rw-r--r--build_scripts/platforms/unix.py167
-rw-r--r--build_scripts/platforms/windows_desktop.py326
-rw-r--r--build_scripts/qtinfo.py237
-rw-r--r--build_scripts/utils.py1075
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