aboutsummaryrefslogtreecommitdiffstats
path: root/build_scripts
diff options
context:
space:
mode:
authorAlexandru Croitor <alexandru.croitor@qt.io>2018-04-23 14:38:39 +0200
committerAlexandru Croitor <alexandru.croitor@qt.io>2018-04-30 09:31:17 +0000
commit6fe563b2aa15c2c23369acce353be69f6ea84c77 (patch)
treee0ef0c6f7ae4106424a9202745651d59e70bd558 /build_scripts
parent1d6bad63bcb6720078de1e78c1b495bf08d1c65b (diff)
Split and move various setup.py parts into different files
This is an initial effort to clean up setup.py. A new directory called build_scripts contains most of the logic for building, leaving setup.py as an entry point. The build_scripts directory contains the usual qtinfo, utils, and the setup.py content has been split into main.py and platform specific files under platforms subfolder. The testrunner script has been modified to find the new location of the utils module. Task-number: PYSIDE-558 Change-Id: I3e041d5116ca5c3f96c789317303b65a7b1bbd70 Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
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