aboutsummaryrefslogtreecommitdiffstats
path: root/build_scripts
diff options
context:
space:
mode:
Diffstat (limited to 'build_scripts')
-rw-r--r--build_scripts/__init__.py77
-rw-r--r--build_scripts/build_info_collector.py311
-rw-r--r--build_scripts/build_scripts.pyproject6
-rw-r--r--build_scripts/config.py193
-rw-r--r--build_scripts/log.py15
-rw-r--r--build_scripts/main.py1555
-rw-r--r--build_scripts/options.py603
-rw-r--r--build_scripts/platforms/__init__.py40
-rw-r--r--build_scripts/platforms/linux.py181
-rw-r--r--build_scripts/platforms/macos.py181
-rw-r--r--build_scripts/platforms/unix.py340
-rw-r--r--build_scripts/platforms/windows_desktop.py490
-rw-r--r--build_scripts/qfp_tool.py (renamed from build_scripts/qp5_tool.py)223
-rw-r--r--build_scripts/qtinfo.py488
-rw-r--r--build_scripts/setup_runner.py252
-rw-r--r--build_scripts/utils.py1015
-rw-r--r--build_scripts/wheel_files.py1036
-rw-r--r--build_scripts/wheel_override.py287
-rw-r--r--build_scripts/wheel_utils.py124
19 files changed, 4739 insertions, 2678 deletions
diff --git a/build_scripts/__init__.py b/build_scripts/__init__.py
index 571d37492..128bb2394 100644
--- a/build_scripts/__init__.py
+++ b/build_scripts/__init__.py
@@ -1,38 +1,39 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+PYSIDE = 'pyside6'
+PYSIDE_MODULE = 'PySide6'
+SHIBOKEN = 'shiboken6'
+
+PYSIDE_PYTHON_TOOLS = ["metaobjectdump",
+ "deploy",
+ "android_deploy",
+ "project",
+ "qml",
+ "qtpy2cpp",
+ "genpyi"]
+
+PYSIDE_UNIX_BIN_TOOLS = ["lupdate",
+ "lrelease",
+ "qmllint",
+ "qmlformat",
+ "qmlls",
+ "qsb",
+ "balsam",
+ "balsamui"]
+
+# tools that are bundled as .app in macOS, but are normal executables in Linux and Windows
+PYSIDE_UNIX_BUNDLED_TOOLS = ["assistant",
+ "designer",
+ "linguist"]
+
+PYSIDE_LINUX_BIN_TOOLS = PYSIDE_UNIX_BIN_TOOLS + PYSIDE_UNIX_BUNDLED_TOOLS
+
+PYSIDE_UNIX_LIBEXEC_TOOLS = ["uic",
+ "rcc",
+ "qmltyperegistrar",
+ "qmlimportscanner",
+ "qmlcachegen"]
+
+# all Qt tools are in 'bin' folder in Windows
+PYSIDE_WINDOWS_BIN_TOOLS = PYSIDE_UNIX_LIBEXEC_TOOLS + PYSIDE_LINUX_BIN_TOOLS
diff --git a/build_scripts/build_info_collector.py b/build_scripts/build_info_collector.py
new file mode 100644
index 000000000..30ce187c8
--- /dev/null
+++ b/build_scripts/build_info_collector.py
@@ -0,0 +1,311 @@
+# Copyright (C) 2021 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import os
+import platform
+import sys
+import sysconfig
+from pathlib import Path
+from sysconfig import get_config_var
+
+from setuptools.errors import SetupError
+
+from .log import log
+from .options import OPTION
+from .qtinfo import QtInfo
+from .utils import configure_cmake_project, parse_cmake_project_message_info
+from .wheel_utils import get_qt_version
+
+
+# Return a prefix suitable for the _install/_build directory
+def prefix():
+ virtual_env_name = os.environ.get('VIRTUAL_ENV', None)
+ has_virtual_env = False
+ if virtual_env_name is not None:
+ name = Path(virtual_env_name).name
+ has_virtual_env = True
+ else:
+ name = "qfp"
+ if OPTION["DEBUG"]:
+ name += "d"
+ if is_debug_python():
+ name += "p"
+ if OPTION["LIMITED_API"] == "yes":
+ name += "a"
+ return Path(name), has_virtual_env
+
+
+def is_debug_python():
+ return getattr(sys, "gettotalrefcount", None) is not None
+
+
+def _get_py_library_win(build_type, py_version, py_prefix, py_libdir,
+ py_include_dir):
+ """Helper for finding the Python library on Windows"""
+ if py_include_dir is None or not Path(py_include_dir).exists():
+ py_include_dir = Path(py_prefix) / "include"
+ if py_libdir is None or not Path(py_libdir).exists():
+ # For virtual environments on Windows, the py_prefix will contain a
+ # path pointing to it, instead of the system Python installation path.
+ # Since INCLUDEPY contains a path to the system location, we use the
+ # same base directory to define the py_libdir variable.
+ py_libdir = Path(py_include_dir).parent / "libs"
+ if not py_libdir.is_dir():
+ raise SetupError("Failed to locate the 'libs' directory")
+ dbg_postfix = "_d" if build_type == "Debug" else ""
+ if OPTION["MAKESPEC"] == "mingw":
+ static_lib_name = f"libpython{py_version.replace('.', '')}{dbg_postfix}.a"
+ return Path(py_libdir) / static_lib_name
+ v = py_version.replace(".", "")
+ python_lib_name = f"python{v}{dbg_postfix}.lib"
+ return Path(py_libdir) / python_lib_name
+
+
+def _get_py_library_unix(build_type, py_version, py_prefix, py_libdir,
+ py_include_dir):
+ """Helper for finding the Python library on UNIX"""
+ if py_libdir is None or not Path(py_libdir).exists():
+ py_libdir = Path(py_prefix) / "lib"
+ if py_include_dir is None or not Path(py_include_dir).exists():
+ directory = f"include/python{py_version}"
+ py_include_dir = Path(py_prefix) / directory
+ lib_exts = ['.so']
+ if sys.platform == 'darwin':
+ lib_exts.append('.dylib')
+ lib_suff = getattr(sys, 'abiflags', None)
+ 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')
+
+ libs_tried = []
+ for lib_ext in lib_exts:
+ lib_name = f"libpython{py_version}{lib_suff}{lib_ext}"
+ py_library = Path(py_libdir) / lib_name
+ if py_library.exists():
+ return py_library
+ libs_tried.append(py_library)
+
+ # Try to find shared libraries which have a multi arch
+ # suffix.
+ py_multiarch = get_config_var("MULTIARCH")
+ if py_multiarch:
+ try_py_libdir = Path(py_libdir) / py_multiarch
+ libs_tried = []
+ for lib_ext in lib_exts:
+ lib_name = f"libpython{py_version}{lib_suff}{lib_ext}"
+ py_library = try_py_libdir / lib_name
+ if py_library.exists():
+ return py_library
+ libs_tried.append(py_library)
+
+ # PYSIDE-535: See if this is PyPy.
+ if hasattr(sys, "pypy_version_info"):
+ vi = sys.version_info[:2]
+ version_quirk = ".".join(map(str, vi)) if vi >= (3, 9) else "3"
+ pypy_libdir = Path(py_libdir).parent / "bin"
+ for lib_ext in lib_exts:
+ lib_name = f"libpypy{version_quirk}-c{lib_ext}"
+ pypy_library = pypy_libdir / lib_name
+ if pypy_library.exists():
+ return pypy_library
+ libs_tried.append(pypy_library)
+ _libs_tried = ', '.join(str(lib) for lib in libs_tried)
+ raise SetupError(f"Failed to locate the Python library with {_libs_tried}")
+
+
+def get_py_library(build_type, py_version, py_prefix, py_libdir, py_include_dir):
+ """Find the Python library"""
+ if sys.platform == "win32":
+ py_library = _get_py_library_win(build_type, py_version, py_prefix,
+ py_libdir, py_include_dir)
+ else:
+ py_library = _get_py_library_unix(build_type, py_version, py_prefix,
+ py_libdir, py_include_dir)
+ if str(py_library).endswith('.a'):
+ # Python was compiled as a static library
+ log.error(f"Failed to locate a dynamic Python library, using {py_library}")
+ return py_library
+
+
+class BuildInfoCollectorMixin(object):
+ build_base: str
+ build_lib: str
+ cmake: str
+ cmake_toolchain_file: str
+ internal_cmake_install_dir_query_file_path: str
+ is_cross_compile: bool
+ plat_name: str
+ python_target_path: str
+
+ def __init__(self):
+ pass
+
+ def collect_and_assign(self):
+ script_dir = Path.cwd()
+
+ # build_base is not set during install command, so we default to
+ # the 'build command's build_base value ourselves.
+ build_base = self.build_base
+ if not build_base:
+ self.build_base = "build"
+ build_base = self.build_base
+
+ sources_dir = script_dir / "sources"
+
+ if self.is_cross_compile:
+ config_tests_dir = script_dir / build_base / "config.tests"
+ python_target_info_dir = (sources_dir / "shiboken6" / "config.tests"
+ / "target_python_info")
+ cmake_cache_args = []
+
+ if self.python_target_path:
+ cmake_cache_args.append(("Python_ROOT_DIR", self.python_target_path))
+
+ if self.cmake_toolchain_file:
+ cmake_cache_args.append(("CMAKE_TOOLCHAIN_FILE", self.cmake_toolchain_file))
+ python_target_info_output = configure_cmake_project(
+ python_target_info_dir,
+ self.cmake,
+ temp_prefix_build_path=config_tests_dir,
+ cmake_cache_args=cmake_cache_args)
+ python_target_info = parse_cmake_project_message_info(python_target_info_output)
+ self.python_target_info = python_target_info
+
+ build_type = "Debug" if OPTION["DEBUG"] else "Release"
+ if OPTION["RELWITHDEBINFO"]:
+ build_type = 'RelWithDebInfo'
+
+ # Prepare parameters
+ if not self.is_cross_compile:
+ platform_arch = platform.architecture()[0]
+ self.py_arch = platform_arch[:-3]
+
+ py_executable = sys.executable
+ _major, _minor, *_ = sys.version_info
+ py_version = f"{_major}.{_minor}"
+ py_include_dir = get_config_var("INCLUDEPY")
+ py_libdir = get_config_var("LIBDIR")
+ # sysconfig.get_config_var('prefix') returned the
+ # virtual environment base directory, but
+ # sysconfig.get_config_var returns the system's prefix.
+ # We use 'base' instead (although, platbase points to the
+ # same location)
+ py_prefix = get_config_var("base")
+ if not py_prefix or not Path(py_prefix).exists():
+ py_prefix = sys.prefix
+ self.py_prefix = py_prefix
+ py_prefix = Path(py_prefix)
+ if sys.platform == "win32":
+ py_scripts_dir = py_prefix / "Scripts"
+ else:
+ py_scripts_dir = py_prefix / "bin"
+ self.py_scripts_dir = py_scripts_dir
+ else:
+ # We don't look for an interpreter when cross-compiling.
+ py_executable = None
+
+ python_info = self.python_target_info['python_info']
+ py_version = python_info['version'].split('.')
+ py_version = f"{py_version[0]}.{py_version[1]}"
+ py_include_dir = python_info['include_dirs']
+ py_libdir = python_info['library_dirs']
+ py_library = python_info['libraries']
+ self.py_library = py_library
+
+ # Prefix might not be set because the project that extracts
+ # the info is using internal API to get it. It shouldn't be
+ # critical though, because we don't really use neither
+ # py_prefix nor py_scripts_dir in important places
+ # when cross-compiling.
+ if 'prefix' in python_info:
+ py_prefix = python_info['prefix']
+ self.py_prefix = Path(py_prefix).resolve()
+
+ py_scripts_dir = self.py_prefix / 'bin'
+ if py_scripts_dir.exists():
+ self.py_scripts_dir = py_scripts_dir
+ else:
+ self.py_scripts_dir = None
+ else:
+ py_prefix = None
+ self.py_prefix = py_prefix
+ self.py_scripts_dir = None
+
+ self.qtinfo = QtInfo()
+ qt_version = get_qt_version()
+
+ # Used for test blacklists and registry test.
+ if self.is_cross_compile:
+ # Querying the host platform architecture makes no sense when cross-compiling.
+ build_classifiers = f"py{py_version}-qt{qt_version}-{self.plat_name}-"
+ else:
+ build_classifiers = f"py{py_version}-qt{qt_version}-{platform.architecture()[0]}-"
+ if hasattr(sys, "pypy_version_info"):
+ pypy_version = ".".join(map(str, sys.pypy_version_info[:3]))
+ build_classifiers += f"pypy.{pypy_version}-"
+ build_classifiers += f"{build_type.lower()}"
+ self.build_classifiers = build_classifiers
+
+ venv_prefix, has_virtual_env = prefix()
+
+ # The virtualenv name serves as the base of the build dir
+ # and we consider it is distinct enough that we don't have to
+ # append the build classifiers, thus keeping dir names shorter.
+ build_name = f"{venv_prefix}"
+ if self.is_cross_compile and has_virtual_env:
+ build_name += f"-{self.plat_name}"
+
+ # If short paths are requested and no virtual env is found, at
+ # least append the python version for more uniqueness.
+ if OPTION["SHORTER_PATHS"] and not has_virtual_env:
+ build_name += f"-p{py_version}"
+ # If no virtual env is found, use build classifiers for
+ # uniqueness.
+ elif not has_virtual_env:
+ build_name += f"-{self.build_classifiers}"
+
+ common_prefix_dir = script_dir / build_base
+ build_dir = common_prefix_dir / build_name / "build"
+ install_dir = common_prefix_dir / build_name / "install"
+
+ # Change the setuptools build_lib dir to be under the same
+ # directory where the cmake build and install dirs are so
+ # there's a common subdirectory for all build-related dirs.
+ # Example:
+ # Replaces
+ # build/lib.macosx-10.14-x86_64-3.7' with
+ # build/{venv_prefix}/package'
+ setup_tools_build_lib_dir = common_prefix_dir / build_name / "package"
+ self.build_lib = setup_tools_build_lib_dir
+
+ self.script_dir = Path(script_dir)
+ self.sources_dir = Path(sources_dir)
+ self.build_dir = Path(build_dir)
+ self.install_dir = Path(install_dir)
+ self.py_executable = Path(py_executable) if py_executable else None
+ self.py_include_dir = Path(py_include_dir)
+
+ if not self.is_cross_compile:
+ self.py_library = get_py_library(build_type, py_version, py_prefix,
+ py_libdir, py_include_dir)
+ self.py_version = py_version
+ self.build_type = build_type
+
+ if self.is_cross_compile:
+ site_packages_no_prefix = self.python_target_info['python_info']['site_packages_dir']
+ self.site_packages_dir = install_dir / site_packages_no_prefix
+ else:
+ # Setuptools doesn't have an equivalent of a get_python_lib with a
+ # prefix, so we build the path manually:
+ # self.site_packages_dir = sconfig.get_python_lib(1, 0, prefix=install_dir)
+ _base = sysconfig.get_paths()["data"]
+ _purelib = sysconfig.get_paths()["purelib"]
+ assert _base in _purelib
+ self.site_packages_dir = f"{install_dir}{_purelib.replace(_base, '')}"
+
+ def post_collect_and_assign(self):
+ # self.build_lib is only available after the base class
+ # finalize_options is called.
+ self.st_build_dir = self.script_dir / self.build_lib
diff --git a/build_scripts/build_scripts.pyproject b/build_scripts/build_scripts.pyproject
index 604419c10..77f1d0485 100644
--- a/build_scripts/build_scripts.pyproject
+++ b/build_scripts/build_scripts.pyproject
@@ -1,6 +1,8 @@
{
- "files": ["main.py", "__init__.py", "config.py", "options.py", "qtinfo.py",
- "setup_runner.py", "utils.py", "wheel_override.py",
+ "files": ["main.py", "__init__.py", "build_info_collector.py",
+ "config.py", "options.py", "qtinfo.py",
+ "setup_runner.py", "utils.py",
+ "wheel_files.py", "wheel_override.py", "wheel_utils.py",
"platforms/__init__.py", "platforms/linux.py",
"platforms/macos.py", "platforms/unix.py",
"platforms/windows_desktop.py",
diff --git a/build_scripts/config.py b/build_scripts/config.py
index 4ec2af3de..0a6eebf78 100644
--- a/build_scripts/config.py
+++ b/build_scripts/config.py
@@ -1,44 +1,12 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-import os
-import distutils.log as log
+# Copyright (C) 2018 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import sys
+from .log import log, LogLevel
+from pathlib import Path
+
+from . import PYSIDE, PYSIDE_MODULE, SHIBOKEN
+from .utils import available_pyside_tools
class Config(object):
@@ -57,11 +25,11 @@ class Config(object):
self.invocation_type = None
# The type of the top-level build.
- # all - build shiboken2 module, shiboken2-generator and PySide2
+ # all - build shiboken6 module, shiboken6-generator and PySide6
# modules
- # shiboken2 - build only shiboken2 module
- # shiboken2-generator - build only the shiboken2-generator
- # pyside2 - build only PySide2 modules
+ # shiboken6 - build only shiboken6 module
+ # shiboken6-generator - build only the shiboken6-generator
+ # pyside6 - build only PySide6 modules
self.build_type = None
# The internal build type, used for internal invocations of
@@ -70,36 +38,47 @@ class Config(object):
# Options that can be given to --build-type and
# --internal-build-type
- self.shiboken_module_option_name = "shiboken2"
- self.shiboken_generator_option_name = "shiboken2-generator"
- self.pyside_option_name = "pyside2"
+ self.shiboken_module_option_name = SHIBOKEN
+ self.shiboken_generator_option_name = f"{SHIBOKEN}-generator"
+ self.pyside_option_name = PYSIDE
# Names to be passed to setuptools.setup() name key,
# so not package name, but rather project name as it appears
# in the wheel name and on PyPi.
- self.shiboken_module_st_name = "shiboken2"
- self.shiboken_generator_st_name = "shiboken2-generator"
- self.pyside_st_name = "PySide2"
+ self.shiboken_module_st_name = SHIBOKEN
+ self.shiboken_generator_st_name = f"{SHIBOKEN}-generator"
+ self.pyside_st_name = PYSIDE_MODULE
+
+ # Path to CMake toolchain file when intending to cross compile
+ # the project.
+ self.cmake_toolchain_file = None
+
+ # Store where host shiboken is built during a cross-build.
+ self.shiboken_host_query_path = None
# Used by check_allowed_python_version to validate the
# interpreter version.
self.python_version_classifiers = [
'Programming Language :: Python',
- 'Programming Language :: Python :: 2',
- 'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
- 'Programming Language :: Python :: 3.5',
- 'Programming Language :: Python :: 3.6',
- 'Programming Language :: Python :: 3.7',
- 'Programming Language :: Python :: 3.8',
+ 'Programming Language :: Python :: 3.9',
+ 'Programming Language :: Python :: 3.10',
+ 'Programming Language :: Python :: 3.11',
+ 'Programming Language :: Python :: 3.12',
]
self.setup_script_dir = None
- def init_config(self, build_type=None, internal_build_type=None,
- cmd_class_dict=None, package_version=None,
- ext_modules=None, setup_script_dir=None,
- quiet=False):
+ def init_config(self,
+ build_type=None,
+ internal_build_type=None,
+ cmd_class_dict=None,
+ package_version=None,
+ ext_modules=None,
+ setup_script_dir=None,
+ cmake_toolchain_file=None,
+ log_level=LogLevel.INFO,
+ qt_install_path: Path = None):
"""
Sets up the global singleton config which is used in many parts
of the setup process.
@@ -120,7 +99,9 @@ class Config(object):
else:
self.build_type = self._build_type_all
- self.setup_script_dir = setup_script_dir
+ self.setup_script_dir = Path(setup_script_dir)
+
+ self.cmake_toolchain_file = cmake_toolchain_file
setup_kwargs = {}
setup_kwargs['long_description'] = self.get_long_description()
@@ -134,11 +115,10 @@ class Config(object):
setup_kwargs['zip_safe'] = False
setup_kwargs['cmdclass'] = cmd_class_dict
setup_kwargs['version'] = package_version
- setup_kwargs['python_requires'] = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <3.9"
+ setup_kwargs['python_requires'] = ">=3.9, <3.13"
-
- if quiet:
- # Tells distutils / setuptools to be quiet, and only print warnings or errors.
+ if log_level == LogLevel.QUIET:
+ # Tells setuptools to be quiet, and only print warnings or errors.
# Makes way less noise in the terminal when building.
setup_kwargs['verbose'] = 0
@@ -192,6 +172,8 @@ class Config(object):
'Topic :: Software Development :: Widget Sets'])
setup_kwargs['classifiers'] = common_classifiers
+ package_name = self.package_name()
+
if self.internal_build_type == self.shiboken_module_option_name:
setup_kwargs['name'] = self.shiboken_module_st_name
setup_kwargs['description'] = "Python / C++ bindings helper module"
@@ -200,25 +182,39 @@ class Config(object):
elif self.internal_build_type == self.shiboken_generator_option_name:
setup_kwargs['name'] = self.shiboken_generator_st_name
setup_kwargs['description'] = "Python / C++ bindings generator"
- setup_kwargs['install_requires'] = ["{}=={}".format(self.shiboken_module_st_name, package_version)]
+ setup_kwargs['install_requires'] = [
+ f"{self.shiboken_module_st_name}=={package_version}"
+ ]
setup_kwargs['entry_points'] = {
'console_scripts': [
- 'shiboken2 = {}.scripts.shiboken_tool:main'.format(self.package_name()),
+ f'{SHIBOKEN} = {package_name}.scripts.shiboken_tool:main',
+ f'{SHIBOKEN}-genpyi = {package_name}.scripts.shiboken_tool:genpyi',
]
}
elif self.internal_build_type == self.pyside_option_name:
setup_kwargs['name'] = self.pyside_st_name
- setup_kwargs['description'] = "Python bindings for the Qt cross-platform application and UI framework"
- setup_kwargs['install_requires'] = ["{}=={}".format(self.shiboken_module_st_name, package_version)]
- setup_kwargs['entry_points'] = {
- 'console_scripts': [
- 'pyside2-uic = {}.scripts.pyside_tool:uic'.format(self.package_name()),
- 'pyside2-rcc = {}.scripts.pyside_tool:rcc'.format(self.package_name()),
- 'pyside2-designer= {}.scripts.pyside_tool:designer'.format(self.package_name()),
- 'pyside2-lupdate = {}.scripts.pyside_tool:main'.format(self.package_name()),
- ]
- }
+ setup_kwargs['description'] = ("Python bindings for the Qt cross-platform application "
+ "and UI framework")
+ setup_kwargs['install_requires'] = [
+ f"{self.shiboken_module_st_name}=={package_version}"
+ ]
+ if qt_install_path:
+ _pyside_tools = available_pyside_tools(qt_tools_path=qt_install_path)
+
+ # replacing pyside6-android_deploy by pyside6-android-deploy for consistency
+ # Also, the tool should not exist in any other platform than Linux
+ _console_scripts = []
+ if ("android_deploy" in _pyside_tools) and sys.platform.startswith("linux"):
+ _console_scripts = [(f"{PYSIDE}-android-deploy ="
+ " PySide6.scripts.pyside_tool:android_deploy")]
+ _pyside_tools.remove("android_deploy")
+
+ _console_scripts.extend([f'{PYSIDE}-{tool} = {package_name}.scripts.pyside_tool:'
+ f'{tool}' for tool in _pyside_tools])
+
+ setup_kwargs['entry_points'] = {'console_scripts': _console_scripts}
+
self.setup_kwargs = setup_kwargs
def get_long_description(self):
@@ -226,19 +222,19 @@ class Config(object):
changes_filename = 'CHANGES.rst'
if self.is_internal_shiboken_module_build():
- readme_filename = 'README.shiboken2.md'
+ readme_filename = f'README.{SHIBOKEN}.md'
elif self.is_internal_shiboken_generator_build():
- readme_filename = 'README.shiboken2-generator.md'
+ readme_filename = f'README.{SHIBOKEN}-generator.md'
elif self.is_internal_pyside_build():
- readme_filename = 'README.pyside2.md'
+ readme_filename = f'README.{PYSIDE}.md'
content = ''
changes = ''
try:
- with open(os.path.join(self.setup_script_dir, readme_filename)) as f:
+ with open(self.setup_script_dir / readme_filename) as f:
readme = f.read()
except Exception as e:
- log.error("Couldn't read contents of {}.".format(readme_filename))
+ log.error(f"Couldn't read contents of {readme_filename}. {e}")
raise
# Don't include CHANGES.rst for now, because we have not decided
@@ -246,15 +242,15 @@ class Config(object):
include_changes = False
if include_changes:
try:
- with open(os.path.join(self.setup_script_dir, changes_filename)) as f:
+ with open(self.setup_script_dir / changes_filename) as f:
changes = f.read()
except Exception as e:
- log.error("Couldn't read contents of {}".format(changes_filename))
+ log.error(f"Couldn't read contents of {changes_filename}. {e}")
raise
content += readme
if changes:
- content += "\n\n" + changes
+ content += f"\n\n{changes}"
return content
@@ -267,11 +263,11 @@ class Config(object):
dashes.
"""
if self.is_internal_shiboken_module_build():
- return "shiboken2"
+ return SHIBOKEN
elif self.is_internal_shiboken_generator_build():
- return "shiboken2_generator"
+ return f"{SHIBOKEN}_generator"
elif self.is_internal_pyside_build():
- return "PySide2"
+ return PYSIDE_MODULE
else:
return None
@@ -301,8 +297,8 @@ class Config(object):
the actual module packages are located.
For example when building the shiboken module, setuptools will
- expect to find the "shiboken2" module sources under
- "sources/shiboken2/shibokenmodule".
+ expect to find the "shiboken6" module sources under
+ "sources/{SHIBOKEN}/shibokenmodule".
This is really just to satisfy some checks in setuptools
build_py command, and if we ever properly implement the develop
@@ -310,7 +306,7 @@ class Config(object):
"""
if self.is_internal_shiboken_module_build():
return {
- self.package_name(): "sources/shiboken2/shibokenmodule"
+ self.package_name(): f"sources/{SHIBOKEN}/shibokenmodule"
}
elif self.is_internal_shiboken_generator_build():
# This is left empty on purpose, because the shiboken
@@ -318,7 +314,7 @@ class Config(object):
return {}
elif self.is_internal_pyside_build():
return {
- self.package_name(): "sources/pyside2/PySide2",
+ self.package_name(): f"sources/{PYSIDE}/{PYSIDE_MODULE}",
}
else:
return {}
@@ -329,9 +325,9 @@ class Config(object):
:return: A list of directory names under the sources directory.
"""
if self.is_internal_shiboken_module_build() or self.is_internal_shiboken_generator_build():
- return ['shiboken2']
+ return [SHIBOKEN]
elif self.is_internal_pyside_build():
- return ['pyside2', 'pyside2-tools']
+ return [PYSIDE, 'pyside-tools']
return None
def set_is_top_level_invocation(self):
@@ -358,6 +354,11 @@ class Config(object):
def is_top_level_build_pyside(self):
return self.build_type == self.pyside_option_name
+ def is_cross_compile(self):
+ if not self.cmake_toolchain_file:
+ return False
+ return True
+
def set_internal_build_type(self, internal_build_type):
self.internal_build_type = internal_build_type
@@ -374,7 +375,7 @@ class Config(object):
"""
Used to skip certain build rules and output, when we know that
the CMake build of shiboken was already done as part of the
- top-level "all" build when shiboken2-module was built.
+ top-level "all" build when shiboken6-module was built.
"""
return self.is_internal_shiboken_generator_build() and self.is_top_level_build_all()
diff --git a/build_scripts/log.py b/build_scripts/log.py
new file mode 100644
index 000000000..c9ccf3fb9
--- /dev/null
+++ b/build_scripts/log.py
@@ -0,0 +1,15 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import logging
+
+from enum import Enum
+
+logging.basicConfig(format="[%(levelname)s]: %(message)s", level=logging.INFO)
+log = logging.getLogger("qtforpython")
+
+
+class LogLevel(Enum):
+ QUIET = 1
+ INFO = 2
+ VERBOSE = 3
diff --git a/build_scripts/main.py b/build_scripts/main.py
index 55cc6a882..9a8d4fb3f 100644
--- a/build_scripts/main.py
+++ b/build_scripts/main.py
@@ -1,91 +1,60 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from __future__ import print_function
-from distutils.version import LooseVersion
+# Copyright (C) 2018 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+import importlib
import os
+import platform
+import re
+import sys
+import sysconfig
import time
-from .config import config
-from .utils import memoize, get_python_dict
-from .options import OPTION
-
-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")
-
-start_time = int(time.time())
-
+from packaging.version import parse as parse_version
+from pathlib import Path
+from shutil import copytree, rmtree
+from textwrap import dedent
-def elapsed():
- return int(time.time()) - start_time
+# PYSIDE-1760: Pre-load setuptools modules early to avoid racing conditions.
+# may be touched (should be avoided anyway, btw.)
+# Note: This bug is only visible when tools like pyenv are not used. They have some
+# pre-loading effect so that setuptools is already in the cache, hiding the problem.
+from setuptools import Command, Extension
+from setuptools.command.bdist_egg import bdist_egg as _bdist_egg
+from setuptools.command.build_ext import build_ext as _build_ext
+from setuptools.command.build_py import build_py as _build_py
+from setuptools.command.build import build as _build
+from setuptools.command.develop import develop as _develop
+from setuptools.command.install import install as _install
+from setuptools.command.install_lib import install_lib as _install_lib
+from setuptools.command.install_scripts import install_scripts # noqa: preload only
+from .log import log, LogLevel
+from setuptools.errors import SetupError
-@memoize
-def get_package_timestamp():
- """ In a Coin CI build the returned timestamp will be the
- Coin integration id timestamp. For regular builds it's
- just the current timestamp or a user provided one."""
- return OPTION["PACKAGE_TIMESTAMP"] if OPTION["PACKAGE_TIMESTAMP"] else start_time
+from .build_info_collector import BuildInfoCollectorMixin
+from .config import config
+from .options import OPTION, CommandMixin
+from .platforms.unix import prepare_packages_posix
+from .platforms.windows_desktop import prepare_packages_win32
+from .qtinfo import QtInfo
+from .utils import (copydir, copyfile, detect_clang,
+ get_numpy_location, get_python_dict,
+ linux_fix_rpaths_for_library, macos_fix_rpaths_for_library,
+ platform_cmake_options, remove_tree, run_process,
+ run_process_output, update_env_path, which)
+from . import PYSIDE, PYSIDE_MODULE, SHIBOKEN
+from .wheel_override import get_bdist_wheel_override, wheel_module_exists
+from .wheel_utils import (get_package_timestamp, get_package_version,
+ macos_plat_name, macos_pyside_min_deployment_target)
+setup_script_dir = Path.cwd()
+build_scripts_dir = setup_script_dir / 'build_scripts'
+setup_py_path = setup_script_dir / "setup.py"
-@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)
+start_time = time.time()
- final_version = "{}.{}.{}".format(
- d['major_version'], d['minor_version'], d['patch_version'])
- release_version_type = d['release_version_type']
- pre_release_version = d['pre_release_version']
- if pre_release_version and release_version_type:
- final_version += release_version_type + pre_release_version
- if release_version_type.startswith("comm"):
- final_version += "." + release_version_type
- # 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
+def elapsed():
+ return int(time.time() - start_time)
def get_setuptools_extension_modules():
@@ -95,59 +64,58 @@ def get_setuptools_extension_modules():
# future.
extension_args = ('QtCore', [])
extension_kwargs = {}
- if OPTION["LIMITED_API"]:
+ if OPTION["LIMITED_API"] == 'yes':
extension_kwargs['py_limited_api'] = True
extension_modules = [Extension(*extension_args, **extension_kwargs)]
return extension_modules
-# Git submodules: ["submodule_name", "location_relative_to_sources_folder"]
-submodules = [["pyside2-tools"]]
-
-try:
- import setuptools
-except ImportError:
- from ez_setup import use_setuptools
- use_setuptools()
-
-import sys
-import platform
-import re
-
-import distutils.log as log
-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 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
-
-from .qtinfo import QtInfo
-from .utils import rmtree, detect_clang, copyfile, copydir, run_process_output, run_process
-from .utils import update_env_path, init_msvc_env, filter_match
-from .utils import macos_fix_rpaths_for_library
-from .utils import linux_fix_rpaths_for_library
-from .platforms.unix import prepare_packages_posix
-from .platforms.windows_desktop import prepare_packages_win32
-from .wheel_override import wheel_module_exists, get_bdist_wheel_override
-
-from textwrap import dedent
-
-
-def check_allowed_python_version():
- """
- Make sure that setup.py is run with an allowed python version.
- """
-
- import re
+def _get_make(platform_arch, build_type):
+ """Helper for retrieving the make command and CMake generator name"""
+ makespec = OPTION["MAKESPEC"]
+ if makespec == "make":
+ return ("make", "Unix Makefiles")
+ if makespec == "msvc":
+ if not OPTION["NO_JOM"]:
+ jom_path = Path(which("jom"))
+ if jom_path:
+ log.info(f"jom was found in {jom_path}")
+ return (jom_path, "NMake Makefiles JOM")
+ nmake_path = Path(which("nmake"))
+ if nmake_path is None or not nmake_path.exists():
+ raise SetupError("nmake not found")
+ log.info(f"nmake was found in {nmake_path}")
+ if OPTION["JOBS"]:
+ msg = "Option --jobs can only be used with 'jom' on Windows."
+ raise SetupError(msg)
+ return (nmake_path, "NMake Makefiles")
+ if makespec == "mingw":
+ return (Path("mingw32-make"), "mingw32-make")
+ if makespec == "ninja":
+ return (Path("ninja"), "Ninja")
+ raise SetupError(f'Invalid option --make-spec "{makespec}".')
+
+
+def get_make(platform_arch, build_type):
+ """Retrieve the make command and CMake generator name"""
+ (make_path, make_generator) = _get_make(platform_arch, build_type)
+ if not make_path.is_absolute():
+ found_path = Path(which(make_path))
+ if not found_path or not found_path.exists():
+ m = (f"You need the program '{make_path}' on your system path to "
+ f"compile {PYSIDE_MODULE}.")
+ raise SetupError(m)
+ make_path = found_path
+ return (make_path, make_generator)
+
+
+_allowed_versions_cache = None
+
+
+def get_allowed_python_versions():
+ global _allowed_versions_cache
+ if _allowed_versions_cache is not None:
+ return _allowed_versions_cache
pattern = r'Programming Language :: Python :: (\d+)\.(\d+)'
supported = []
@@ -157,206 +125,77 @@ def check_allowed_python_version():
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("Unsupported python version detected. Only these python versions are supported: {}"
- .format(supported))
- sys.exit(1)
+ _allowed_versions_cache = sorted(supported)
+ return _allowed_versions_cache
-qt_src_dir = ''
-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)
-
-# First element is default
-available_mkspecs = ["msvc", "mingw", "ninja"] if sys.platform == "win32" else ["make", "ninja"]
-
-if OPTION["MAKESPEC"] is None:
- OPTION["MAKESPEC"] = available_mkspecs[0]
-
-if OPTION["MAKESPEC"] not in available_mkspecs:
- print('Invalid option --make-spec "{}". Available values are {}'.format(OPTION["MAKESPEC"],
- available_mkspecs))
- 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():
- virtual_env_name = os.environ.get('VIRTUAL_ENV', None)
- if virtual_env_name is not None:
- name = os.path.basename(virtual_env_name)
- else:
- name = "pyside"
- name += str(sys.version_info[0])
- if OPTION["DEBUG"]:
- name += "d"
- if is_debug_python():
- name += "p"
- if OPTION["LIMITED_API"] == "yes" and sys.version_info[0] == 3:
- name += "a"
- return name
-
-
-# Initialize, pull and checkout submodules
-def prepare_sub_modules():
- 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
- need_init_sub_modules = 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):
- need_init_sub_modules = True
- break
-
- if need_init_sub_modules:
- 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(qtinfo.qmake_command))
- sys.exit(1)
+def check_allowed_python_version():
+ """
+ Make sure that setup.py is run with an allowed python version.
+ """
- if LooseVersion(qtinfo.version) < LooseVersion("5.7"):
- log.error("Incompatible Qt version detected: {}. A Qt version >= 5.7 is "
- "required.".format(qt_version))
+ supported = get_allowed_python_versions()
+ this_py = sys.version_info[:2]
+ if this_py not in supported:
+ log.error(f"Unsupported python version detected. Supported versions: {supported}")
sys.exit(1)
- return qt_version
+qt_src_dir = ''
-def prepare_build():
- if (os.path.isdir(".git") and not OPTION["IGNOREGIT"] and not OPTION["ONLYPACKAGE"]
- and not OPTION["REUSE_BUILD"]):
- prepare_sub_modules()
- # Clean up temp build folder.
- for n in ["build"]:
- d = os.path.join(setup_script_dir, n)
- if os.path.isdir(d):
- log.info("Removing {}".format(d))
- try:
- rmtree(d)
- except Exception as e:
- print('***** problem removing "{}"'.format(d))
- print('ignored error: {}'.format(e))
+def prepare_build():
# locate Qt sources for the documentation
if OPTION["QT_SRC"] is None:
- install_prefix = qtinfo.prefix_dir
+ install_prefix = QtInfo().prefix_dir
if install_prefix:
global qt_src_dir
# In-source, developer build
if install_prefix.endswith("qtbase"):
qt_src_dir = install_prefix
else: # SDK: Use 'Src' directory
- qt_src_dir = os.path.join(os.path.dirname(install_prefix), 'Src', 'qtbase')
+ maybe_qt_src_dir = Path(install_prefix).parent / 'Src' / 'qtbase'
+ if maybe_qt_src_dir.exists():
+ qt_src_dir = maybe_qt_src_dir
-class PysideInstall(_install):
+class PysideInstall(_install, CommandMixin):
+
+ user_options = _install.user_options + CommandMixin.mixin_user_options
+
def __init__(self, *args, **kwargs):
+ self.command_name = "install"
_install.__init__(self, *args, **kwargs)
+ CommandMixin.__init__(self)
def initialize_options(self):
_install.initialize_options(self)
- if sys.platform == 'darwin':
+ def finalize_options(self):
+ CommandMixin.mixin_finalize_options(self)
+ _install.finalize_options(self)
+
+ if sys.platform == 'darwin' or self.is_cross_compile:
# Because we change the plat_name to include a correct
- # deployment target on macOS distutils thinks we are
+ # deployment target on macOS setuptools 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")
+ # raise PlatformError("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.
+ # We also do it when cross-compiling. While calling install
+ # command directly is dubious, bdist_wheel calls install
+ # internally before creating a wheel.
self.warn_dir = False
def run(self):
_install.run(self)
- print('*** Install completed ({}s)'.format(elapsed()))
+ log.info(f"--- Install completed ({elapsed()}s)")
class PysideDevelop(_develop):
@@ -391,11 +230,12 @@ class PysideBuildExt(_build_ext):
class PysideBuildPy(_build_py):
def __init__(self, *args, **kwargs):
+ self.command_name = "build_py"
_build_py.__init__(self, *args, **kwargs)
# _install_lib is reimplemented to preserve
-# symlinks when distutils / setuptools copy files to various
+# symlinks when setuptools copy files to various
# directories from the setup tools build dir to the install dir.
class PysideInstallLib(_install_lib):
@@ -404,30 +244,45 @@ class PysideInstallLib(_install_lib):
def install(self):
"""
- Installs files from build/xxx directory into final
- site-packages/PySide2 directory.
+ Installs files from self.build_dir directory into final
+ site-packages/PySide6 directory when the command is 'install'
+ or into build/wheel when command is 'bdist_wheel'.
"""
- if os.path.isdir(self.build_dir):
+ if self.build_dir.is_dir():
# Using our own copydir makes sure to preserve symlinks.
- outfiles = copydir(os.path.abspath(self.build_dir), os.path.abspath(self.install_dir))
+ outfiles = copydir(Path(self.build_dir).resolve(), Path(self.install_dir).resolve())
else:
- self.warn("'{}' does not exist -- no Python modules to install".format(self.build_dir))
+ self.warn(f"'{self.build_dir}' does not exist -- no Python modules to install")
return
return outfiles
-class PysideBuild(_build):
+class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin):
+
+ user_options = _build.user_options + CommandMixin.mixin_user_options
def __init__(self, *args, **kwargs):
+ self.command_name = "build"
_build.__init__(self, *args, **kwargs)
+ CommandMixin.__init__(self)
+ BuildInfoCollectorMixin.__init__(self)
def finalize_options(self):
os_name_backup = os.name
- if sys.platform == 'darwin':
- self.plat_name = PysideBuild.macos_plat_name()
+ CommandMixin.mixin_finalize_options(self)
+ BuildInfoCollectorMixin.collect_and_assign(self)
+
+ use_os_name_hack = False
+ if self.is_cross_compile:
+ use_os_name_hack = True
+ elif sys.platform == 'darwin':
+ self.plat_name = macos_plat_name()
+ use_os_name_hack = True
+
+ if use_os_name_hack:
# This is a hack to circumvent the dubious check in
- # distutils.commands.build -> finalize_options, which only
+ # setuptool.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
@@ -437,14 +292,16 @@ class PysideBuild(_build):
_build.finalize_options(self)
- if sys.platform == 'darwin':
+ # Must come after _build.finalize_options
+ BuildInfoCollectorMixin.post_collect_and_assign(self)
+
+ if use_os_name_hack:
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
@@ -457,256 +314,76 @@ class PysideBuild(_build):
self.build_type = "Release"
self.qtinfo = None
self.build_tests = False
+ self.python_target_info = {}
def run(self):
prepare_build()
- platform_arch = platform.architecture()[0]
- log.info("Python architecture is {}".format(platform_arch))
- self.py_arch = platform_arch[:-3]
-
- 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"
- if OPTION["JOBS"]:
- msg = "Option --jobs can only be used with 'jom' on Windows."
- raise DistutilsSetupError(msg)
- elif OPTION["MAKESPEC"] == "mingw":
- make_name = "mingw32-make"
- make_generator = "MinGW Makefiles"
- elif OPTION["MAKESPEC"] == "ninja":
- make_name = "ninja"
- make_generator = "Ninja"
- 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
- self.py_prefix = py_prefix
- if sys.platform == "win32":
- py_scripts_dir = os.path.join(py_prefix, "Scripts")
- else:
- py_scripts_dir = os.path.join(py_prefix, "bin")
- self.py_scripts_dir = py_scripts_dir
- if py_libdir is None or not os.path.exists(py_libdir):
- if sys.platform == "win32":
- # For virtual environments on Windows, the py_prefix will contain a path pointing
- # to it, instead of the system Python installation path.
- # Since INCLUDEPY contains a path to the system location, we use the same base
- # directory to define the py_libdir variable.
- py_libdir = os.path.join(os.path.dirname(py_include_dir), "libs")
- if not os.path.isdir(py_libdir):
- raise DistutilsSetupError("Failed to locate the 'libs' directory")
- 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))
- dbg_postfix = ""
- if build_type == "Debug":
- dbg_postfix = "_d"
- if sys.platform == "win32":
- if OPTION["MAKESPEC"] == "mingw":
- static_lib_name = "libpython{}{}.a".format(
- py_version.replace(".", ""), dbg_postfix)
- py_library = os.path.join(py_libdir, static_lib_name)
- else:
- python_lib_name = "python{}{}.lib".format(
- py_version.replace(".", ""), dbg_postfix)
- 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 dbg_postfix:
- # For Python2 add a duplicate set of extensions
- # combined with the dbg_postfix, 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 = [dbg_postfix + 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()
+ platform_arch = platform.architecture()[0]
+ (make_path, make_generator) = get_make(platform_arch, self.build_type)
+ self.qtinfo = QtInfo()
# Update the PATH environment variable
- additional_paths = [self.py_scripts_dir, qt_dir]
+ # Don't add Qt to PATH env var, we don't want it to interfere
+ # with CMake's find_package calls which will use
+ # CMAKE_PREFIX_PATH.
+ # Don't add the Python scripts dir to PATH env when
+ # cross-compiling, it could be in the device sysroot (/usr)
+ # which can cause CMake device QtFooToolsConfig packages to be
+ # picked up instead of host QtFooToolsConfig packages.
+ additional_paths = []
+ if self.py_scripts_dir and not self.is_cross_compile:
+ additional_paths.append(self.py_scripts_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")):
- clang_dir = detect_clang()
- if clang_dir[0]:
- clangBinDir = os.path.join(clang_dir[0], 'bin')
- if clangBinDir not in os.environ.get('PATH'):
- log.info("Adding {} as detected by {} to PATH".format(clangBinDir,
- clang_dir[1]))
+ and parse_version(self.qtinfo.version) >= parse_version("5.7.0")):
+ clang_dir, clang_source = detect_clang()
+ if clang_dir:
+ clangBinDir = clang_dir / 'bin'
+ if str(clangBinDir) not in os.environ.get('PATH'):
+ log.info(f"Adding {clangBinDir} as detected by {clang_source} to PATH")
additional_paths.append(clangBinDir)
else:
- raise DistutilsSetupError("Failed to detect Clang when checking "
- "LLVM_INSTALL_DIR, CLANG_INSTALL_DIR, llvm-config")
+ raise SetupError("Failed to detect Clang when checking "
+ "LLVM_INSTALL_DIR, CLANG_INSTALL_DIR, llvm-config")
update_env_path(additional_paths)
- # Used for test blacklists and registry test.
- self.build_classifiers = "py{}-qt{}-{}-{}".format(py_version, qt_version,
- platform.architecture()[0],
- build_type.lower())
- if OPTION["SHORTER_PATHS"]:
- build_name = "p{}".format(py_version)
- else:
- build_name = self.build_classifiers
-
- 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))
-
self.make_path = make_path
self.make_generator = make_generator
- self.debug = OPTION["DEBUG"]
- self.script_dir = script_dir
- self.st_build_dir = os.path.join(self.script_dir, self.build_lib)
- 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"]
# Save the shiboken build dir path for clang deployment
# purposes.
- self.shiboken_build_dir = os.path.join(self.build_dir, "shiboken2")
+ self.shiboken_build_dir = self.build_dir / SHIBOKEN
self.log_pre_build_info()
# Prepare folders
- if not os.path.exists(self.sources_dir):
- log.info("Creating sources folder {}...".format(self.sources_dir))
+ if not self.sources_dir.exists():
+ log.info(f"Creating sources folder {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))
+ if not self.build_dir.exists():
+ log.info(f"Creating build folder {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))
+ if not self.install_dir.exists():
+ log.info(f"Creating install folder {self.install_dir}...")
os.makedirs(self.install_dir)
+ # Write the CMake install path into a file. Is used by
+ # SetupRunner to provide a nicer UX when cross-compiling (no
+ # need to specify a host shiboken path explicitly)
+ if self.internal_cmake_install_dir_query_file_path:
+ with open(self.internal_cmake_install_dir_query_file_path, 'w') as f:
+ f.write(os.fspath(self.install_dir))
+
if (not OPTION["ONLYPACKAGE"]
and not config.is_internal_shiboken_generator_build_and_part_of_top_level_all()):
# Build extensions
@@ -717,14 +394,14 @@ class PysideBuild(_build):
# 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')
+ build_history = setup_script_dir / 'build_history'
+ unique_dir = build_history / timestamp
+ unique_dir.mkdir(parents=True)
+ fpath = unique_dir / 'build_dir.txt'
with open(fpath, 'w') as f:
- print(build_dir, file=f)
+ print(self.build_dir, file=f)
print(self.build_classifiers, file=f)
- log.info("Created {}".format(build_history))
+ log.info(f"Created {build_history}")
if not OPTION["SKIP_PACKAGING"]:
# Build patchelf if needed
@@ -735,212 +412,219 @@ class PysideBuild(_build):
# Build packages
_build.run(self)
+
+ # Keep packaged directories for wheel construction
+ # This is to take advantage of the packaging step
+ # to keep the data in the proper structure to create
+ # a wheel.
+ _path = Path(self.st_build_dir)
+ _wheel_path = _path.parent / "package_for_wheels"
+
+ _project = None
+
+ if config.is_internal_shiboken_module_build():
+ _project = "shiboken6"
+ elif config.is_internal_shiboken_generator_build():
+ _project = "shiboken6_generator"
+ elif config.is_internal_pyside_build():
+ _project = "PySide6"
+
+ if _project is not None:
+ if not _wheel_path.exists():
+ _wheel_path.mkdir(parents=True)
+ _src = Path(_path / _project)
+ _dst = Path(_wheel_path / _project)
+ # Remove the directory in case it exists.
+ # This applies to 'shiboken6', 'shiboken6_generator',
+ # and 'pyside6' inside the 'package_for_wheels' directory.
+ if _dst.exists():
+ log.warning(f'Found directory "{_dst}", removing it first.')
+ remove_tree(_dst)
+
+ try:
+ # This should be copied because the package directory
+ # is used when using the 'install' setup.py instruction.
+ copytree(_src, _dst)
+ except Exception as e:
+ log.warning(f'problem renaming "{self.st_build_dir}"')
+ log.warning(f'ignored error: {type(e).__name__}: {e}')
else:
log.info("Skipped preparing and building packages.")
- print('*** Build completed ({}s)'.format(elapsed()))
+ log.info(f"--- Build completed ({elapsed()}s)")
def log_pre_build_info(self):
if config.is_internal_shiboken_generator_build_and_part_of_top_level_all():
return
- setuptools_install_prefix = get_python_lib(1)
+ setuptools_install_prefix = sysconfig.get_paths()["purelib"]
if OPTION["FINAL_INSTALL_PREFIX"]:
setuptools_install_prefix = OPTION["FINAL_INSTALL_PREFIX"]
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(f"Package version: {get_package_version()}")
+ log.info(f"Build type: {self.build_type}")
+ log.info(f"Build tests: {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(f"Make path: {self.make_path}")
+ log.info(f"Make generator: {self.make_generator}")
+ log.info(f"Make jobs: {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 {st_package_name} will create and touch directories
+ log.info(f"setup.py directory: {self.script_dir}")
+ log.info(f"Build scripts directory: {build_scripts_dir}")
+ log.info(f"Sources directory: {self.sources_dir}")
+ log.info(dedent(f"""
+ Building {config.package_name()} will create and touch directories
in the following order:
- make build directory (py*_build/*/*) ->
- make install directory (py*_install/*/*) ->
- setuptools build directory (build/*/*) ->
+ make build directory ->
+ make install directory ->
+ setuptools build directory ->
setuptools install directory
(usually path-installed-python/lib/python*/site-packages/*)
- """).format(st_package_name=config.package_name()))
- log.info("make build directory: {}".format(self.build_dir))
- log.info("make install directory: {}".format(self.install_dir))
- log.info("setuptools build directory: {}".format(self.st_build_dir))
- log.info("setuptools install directory: {}".format(setuptools_install_prefix))
- log.info(dedent("""
- make-installed site-packages directory: {}
+ """))
+ log.info(f"make build directory: {self.build_dir}")
+ log.info(f"make install directory: {self.install_dir}")
+ log.info(f"setuptools build directory: {self.st_build_dir}")
+ log.info(f"setuptools install directory: {setuptools_install_prefix}")
+ log.info(dedent(f"""
+ make-installed site-packages directory: {self.site_packages_dir}
(only relevant for copying files from 'make install directory'
to 'setuptools build directory'
- """).format(
- self.site_packages_dir))
+ """))
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(self.py_prefix))
- log.info("Python scripts: {}".format(self.py_scripts_dir))
+ log.info(f"Python executable: {self.py_executable}")
+ log.info(f"Python includes: {self.py_include_dir}")
+ log.info(f"Python library: {self.py_library}")
+ log.info(f"Python prefix: {self.py_prefix}")
+ log.info(f"Python scripts: {self.py_scripts_dir}")
+ log.info(f"Python arch: {self.py_arch}")
+
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(f"Qt prefix: {self.qtinfo.prefix_dir}")
+ log.info(f"Qt qmake: {self.qtinfo.qmake_command}")
+ log.info(f"Qt qtpaths: {self.qtinfo.qtpaths_command}")
+ log.info(f"Qt version: {self.qtinfo.version}")
+ log.info(f"Qt bins: {self.qtinfo.bins_dir}")
+ log.info(f"Qt docs: {self.qtinfo.docs_dir}")
+ log.info(f"Qt plugins: {self.qtinfo.plugins_dir}")
log.info("-" * 3)
if sys.platform == 'win32':
- log.info("OpenSSL dll directory: {}".format(OPTION["OPENSSL"]))
+ log.info(f"OpenSSL dll directory: {OPTION['OPENSSL']}")
if sys.platform == 'darwin':
- pyside_macos_deployment_target = (
- PysideBuild.macos_pyside_min_deployment_target()
- )
- log.info("MACOSX_DEPLOYMENT_TARGET set to: {}".format(
- pyside_macos_deployment_target))
+ pyside_macos_deployment_target = (macos_pyside_min_deployment_target())
+ log.info(f"MACOSX_DEPLOYMENT_TARGET set to: {pyside_macos_deployment_target}")
log.info("=" * 30)
- @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 = PysideBuild.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 = PysideBuild.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
- self._patchelf_path = find_executable('patchelf')
+ self._patchelf_path = which('patchelf')
if self._patchelf_path:
- if not os.path.isabs(self._patchelf_path):
- self._patchelf_path = os.path.join(os.getcwd(), self._patchelf_path)
- log.info("Using {} ...".format(self._patchelf_path))
+ self._patchelf_path = Path(self._patchelf_path)
+ if not self._patchelf_path.is_absolute():
+ self._patchelf_path = Path.cwd() / self._patchelf_path
+ log.info(f"Using {self._patchelf_path} ...")
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")
- self._patchelf_path = os.path.join(self.script_dir, "patchelf")
+ else:
+ raise SetupError("patchelf not found")
+
+ def _enable_numpy(self):
+ if OPTION["ENABLE_NUMPY_SUPPORT"] or OPTION["PYSIDE_NUMPY_SUPPORT"]:
+ return True
+ if OPTION["DISABLE_NUMPY_SUPPORT"]:
+ return False
+ if self.is_cross_compile: # Do not search header in host Python
+ return False
+ # Debug builds require numpy to be built in debug mode on Windows
+ # https://numpy.org/devdocs/user/troubleshooting-importerror.html
+ return sys.platform != 'win32' or self.build_type.lower() != 'debug'
def build_extension(self, extension):
# calculate the subrepos folder name
- log.info("Building module {}...".format(extension))
+ log.info(f"Building module {extension}...")
# Prepare folders
os.chdir(self.build_dir)
- module_build_dir = os.path.join(self.build_dir, extension)
- skipflag_file = "{} -skip".format(module_build_dir)
- if os.path.exists(skipflag_file):
- log.info("Skipping {} because {} exists".format(extension, skipflag_file))
+ module_build_dir = self.build_dir / extension
+ skipflag_file = Path(f"{module_build_dir}-skip")
+ if skipflag_file.exists():
+ log.info(f"Skipping {extension} because {skipflag_file} exists")
return
- module_build_exists = os.path.exists(module_build_dir)
+ module_build_exists = module_build_dir.exists()
if module_build_exists:
if not OPTION["REUSE_BUILD"]:
- log.info("Deleting module build folder {}...".format(module_build_dir))
+ log.info(f"Deleting module build folder {module_build_dir}...")
try:
- rmtree(module_build_dir)
+ remove_tree(module_build_dir)
except Exception as e:
- print('***** problem removing "{}"'.format(module_build_dir))
- print('ignored error: {}'.format(e))
+ log.error(f'***** problem removing "{module_build_dir}"')
+ log.error(f'ignored error: {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))
+ log.info(f"Reusing module build folder {module_build_dir}...")
+ if not module_build_dir.exists():
+ log.info(f"Creating module build folder {module_build_dir}...")
os.makedirs(module_build_dir)
os.chdir(module_build_dir)
- module_src_dir = os.path.join(self.sources_dir, extension)
+ module_src_dir = self.sources_dir / extension
# Build module
- cmake_cmd = [OPTION["CMAKE"]]
- if OPTION["QUIET"]:
+ cmake_cmd = [str(OPTION["CMAKE"])]
+ cmake_quiet_build = 1
+ cmake_rule_messages = 0
+ if OPTION["LOG_LEVEL"] == LogLevel.VERBOSE:
# Pass a special custom option, to allow printing a lot less information when doing
# a quiet build.
- cmake_cmd.append('-DQUIET_BUILD=1')
+ cmake_quiet_build = 0
if self.make_generator == "Unix Makefiles":
# Hide progress messages for each built source file.
# Doesn't seem to work if set within the cmake files themselves.
- cmake_cmd.append('-DCMAKE_RULE_MESSAGES=0')
+ cmake_rule_messages = 1
+
+ if OPTION["UNITY"]:
+ cmake_cmd.append("-DCMAKE_UNITY_BUILD=ON")
+ batch_size = OPTION["UNITY_BUILD_BATCH_SIZE"]
+ cmake_cmd.append(f"-DCMAKE_UNITY_BUILD_BATCH_SIZE={batch_size}")
+ log.info("Using UNITY build")
cmake_cmd += [
"-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
+ f"-DBUILD_TESTS={self.build_tests}",
+ f"-DQt5Help_DIR={self.qtinfo.docs_dir}",
+ f"-DCMAKE_BUILD_TYPE={self.build_type}",
+ f"-DCMAKE_INSTALL_PREFIX={self.install_dir}",
+ # Record the minimum/maximum Python version for later use in Shiboken.__init__
+ f"-DMINIMUM_PYTHON_VERSION={get_allowed_python_versions()[0]}",
+ f"-DMAXIMUM_PYTHON_VERSION={get_allowed_python_versions()[-1]}",
+ f"-DQUIET_BUILD={cmake_quiet_build}",
+ f"-DCMAKE_RULE_MESSAGES={cmake_rule_messages}",
+ str(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))
+
+ # When cross-compiling we set Python_ROOT_DIR to tell
+ # FindPython.cmake where to pick up the device python libs.
+ if self.is_cross_compile:
+ if self.python_target_path:
+ cmake_cmd.append(f"-DPython_ROOT_DIR={self.python_target_path}")
+
+ # Host python is needed when cross compiling to run
+ # embedding_generator.py. Pass it as a separate option.
+ cmake_cmd.append(f"-DQFP_PYTHON_HOST_PATH={sys.executable}")
+ else:
+ cmake_cmd.append(f"-DPython_EXECUTABLE={self.py_executable}")
+ cmake_cmd.append(f"-DPython_INCLUDE_DIR={self.py_include_dir}")
+ cmake_cmd.append(f"-DPython_LIBRARY={self.py_library}")
# If a custom shiboken cmake config directory path was provided, pass it to CMake.
if OPTION["SHIBOKEN_CONFIG_DIR"] and config.is_internal_pyside_build():
- if os.path.exists(OPTION["SHIBOKEN_CONFIG_DIR"]):
- log.info("Using custom provided shiboken2 installation: {}"
- .format(OPTION["SHIBOKEN_CONFIG_DIR"]))
- cmake_cmd.append("-DShiboken2_DIR={}".format(OPTION["SHIBOKEN_CONFIG_DIR"]))
+ config_dir = OPTION["SHIBOKEN_CONFIG_DIR"]
+ if config_dir.exists():
+ log.info(f"Using custom provided {SHIBOKEN} installation: {config_dir}")
+ cmake_cmd.append(f"-DShiboken6_DIR={config_dir}")
else:
- log.info("Custom provided shiboken2 installation not found. Path given: {}"
- .format(OPTION["SHIBOKEN_CONFIG_DIR"]))
+
+ log.info(f"Custom provided {SHIBOKEN} installation not found. "
+ f"Path given: {config_dir}")
if OPTION["MODULE_SUBSET"]:
module_sub_set = ''
@@ -950,7 +634,8 @@ class PysideBuild(_build):
if module_sub_set:
module_sub_set += ';'
module_sub_set += m
- cmake_cmd.append("-DMODULES={}".format(module_sub_set))
+ cmake_cmd.append(f"-DMODULES={module_sub_set}")
+
if OPTION["SKIP_MODULES"]:
skip_modules = ''
for m in OPTION["SKIP_MODULES"].split(','):
@@ -959,28 +644,66 @@ class PysideBuild(_build):
if skip_modules:
skip_modules += ';'
skip_modules += m
- cmake_cmd.append("-DSKIP_MODULES={}".format(skip_modules))
+ cmake_cmd.append(f"-DSKIP_MODULES={skip_modules}")
# Add source location for generating documentation
cmake_src_dir = OPTION["QT_SRC"] if OPTION["QT_SRC"] else qt_src_dir
- cmake_cmd.append("-DQT_SRC_DIR={}".format(cmake_src_dir))
- log.info("Qt Source dir: {}".format(cmake_src_dir))
+ if cmake_src_dir:
+ cmake_cmd.append(f"-DQT_SRC_DIR={cmake_src_dir}")
+ if OPTION['NO_QT_TOOLS']:
+ cmake_cmd.append("-DNO_QT_TOOLS=yes")
+ if OPTION['SKIP_DOCS']:
+ log.info("Warning: '--skip-docs' is deprecated and will be removed. "
+ "The documentation is not built by default")
+ if OPTION['BUILD_DOCS']:
+ cmake_cmd.append("-DBUILD_DOCS=yes")
+ log.info(f"Qt Source dir: {cmake_src_dir}")
+
+ # Use Legacy OpenGL to avoid issues on systems like Ubuntu 20.04
+ # which require to manually install the libraries which
+ # were previously linked to the QtGui module in 6.1
+ # https://bugreports.qt.io/browse/QTBUG-89754
+ cmake_cmd.append("-DOpenGL_GL_PREFERENCE=LEGACY")
+
+ if OPTION['AVOID_PROTECTED_HACK']:
+ cmake_cmd.append("-DAVOID_PROTECTED_HACK=1")
+
+ if self._enable_numpy():
+ numpy = get_numpy_location()
+ if numpy:
+ cmake_cmd.append(f"-DNUMPY_INCLUDE_DIR={numpy}")
+ else:
+ log.warning('numpy include directory was not found.')
- if self.build_type.lower() == 'debug':
- cmake_cmd.append("-DPYTHON_DEBUG_LIBRARY={}".format(
- self.py_library))
+ if self.build_type.lower() != 'debug':
+ if OPTION['NO_STRIP']:
+ cmake_cmd.append("-DQFP_NO_STRIP=1")
+ if OPTION['NO_OVERRIDE_OPTIMIZATION_FLAGS']:
+ cmake_cmd.append("-DQFP_NO_OVERRIDE_OPTIMIZATION_FLAGS=1")
if OPTION["LIMITED_API"] == "yes":
cmake_cmd.append("-DFORCE_LIMITED_API=yes")
elif OPTION["LIMITED_API"] == "no":
cmake_cmd.append("-DFORCE_LIMITED_API=no")
elif not OPTION["LIMITED_API"]:
- pass
+ if sys.platform == 'win32' and self.debug:
+ cmake_cmd.append("-DFORCE_LIMITED_API=no")
else:
- raise DistutilsSetupError("option limited-api must be 'yes' or 'no' "
- "(default yes if applicable, i.e. python version >= 3.5)")
+ raise SetupError("option limited-api must be 'yes' or 'no' "
+ "(default yes if applicable, i.e. Python "
+ "version >= 3.9 and release build if on Windows)")
- if OPTION["VERBOSE_BUILD"]:
+ if OPTION["DISABLE_PYI"]:
+ cmake_cmd.append("-DDISABLE_PYI=yes")
+
+ if OPTION["LOG_LEVEL"] == LogLevel.VERBOSE:
cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON")
+ else:
+ cmake_cmd.append("-DCMAKE_VERBOSE_MAKEFILE:BOOL=OFF")
+
+ if OPTION['COMPILER_LAUNCHER']:
+ compiler_launcher = OPTION['COMPILER_LAUNCHER']
+ cmake_cmd.append(f"-DCMAKE_C_COMPILER_LAUNCHER={compiler_launcher}")
+ cmake_cmd.append(f"-DCMAKE_CXX_COMPILER_LAUNCHER={compiler_launcher}")
if OPTION["SANITIZE_ADDRESS"]:
# Some simple sanity checking. Only use at your own risk.
@@ -988,9 +711,9 @@ class PysideBuild(_build):
or sys.platform.startswith('darwin')):
cmake_cmd.append("-DSANITIZE_ADDRESS=ON")
else:
- raise DistutilsSetupError("Address sanitizer can only be used on Linux and macOS.")
+ raise SetupError("Address sanitizer can only be used on Linux and macOS.")
- if extension.lower() == "pyside2":
+ if extension.lower() == PYSIDE:
pyside_qt_conf_prefix = ''
if OPTION["QT_CONF_PREFIX"]:
pyside_qt_conf_prefix = OPTION["QT_CONF_PREFIX"]
@@ -999,13 +722,15 @@ class PysideBuild(_build):
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))
+ cmake_cmd.append(f"-DPYSIDE_QT_CONF_PREFIX={pyside_qt_conf_prefix}")
+
+ if OPTION["STANDALONE"]:
+ cmake_cmd.append("-DSTANDALONE:BOOL=ON")
# Pass package version to CMake, so this string can be
# embedded into _config.py file.
package_version = get_package_version()
- cmake_cmd.append("-DPACKAGE_SETUP_PY_PACKAGE_VERSION={}".format(package_version))
+ cmake_cmd.append(f"-DPACKAGE_SETUP_PY_PACKAGE_VERSION={package_version}")
# In case if this is a snapshot build, also pass the
# timestamp as a separate value, because it is the only
@@ -1013,17 +738,17 @@ class PysideBuild(_build):
timestamp = ''
if OPTION["SNAPSHOT_BUILD"]:
timestamp = get_package_timestamp()
- cmake_cmd.append("-DPACKAGE_SETUP_PY_PACKAGE_TIMESTAMP={}".format(timestamp))
+ cmake_cmd.append(f"-DPACKAGE_SETUP_PY_PACKAGE_TIMESTAMP={timestamp}")
+
+ if extension.lower() in [SHIBOKEN]:
+ cmake_cmd.append("-DUSE_PYTHON_VERSION=3.9")
- if extension.lower() in ["shiboken2", "pyside2-tools"]:
- cmake_cmd.append("-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=yes")
- if sys.version_info[0] > 2:
- cmake_cmd.append("-DUSE_PYTHON_VERSION=3.3")
+ cmake_cmd += platform_cmake_options()
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"]))
+ cmake_cmd.append(f"-DCMAKE_OSX_ARCHITECTURES:STRING={OPTION['MACOS_ARCH']}")
if OPTION["MACOS_USE_LIBCPP"]:
# Explicitly link the libc++ standard library (useful
@@ -1037,60 +762,111 @@ class PysideBuild(_build):
cmake_cmd.append("-DOSX_USE_LIBCPP=ON")
if OPTION["MACOS_SYSROOT"]:
- cmake_cmd.append("-DCMAKE_OSX_SYSROOT={}".format(
- OPTION["MACOS_SYSROOT"]))
+ cmake_cmd.append(f"-DCMAKE_OSX_SYSROOT={OPTION['MACOS_SYSROOT']}")
else:
latest_sdk_path = run_process_output(['xcrun', '--sdk', 'macosx',
'--show-sdk-path'])
if latest_sdk_path:
latest_sdk_path = latest_sdk_path[0]
- cmake_cmd.append("-DCMAKE_OSX_SYSROOT={}".format(
- latest_sdk_path))
+ cmake_cmd.append(f"-DCMAKE_OSX_SYSROOT={latest_sdk_path}")
# Set macOS minimum deployment target (version).
# This is required so that calling
- # run_process -> distutils.spawn()
+ # run_process -> subprocess.call()
# 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 = PysideBuild.macos_pyside_min_deployment_target()
- cmake_cmd.append("-DCMAKE_OSX_DEPLOYMENT_TARGET={}".format(deployment_target))
+ deployment_target = macos_pyside_min_deployment_target()
+ cmake_cmd.append(f"-DCMAKE_OSX_DEPLOYMENT_TARGET={deployment_target}")
os.environ['MACOSX_DEPLOYMENT_TARGET'] = deployment_target
+ if OPTION["BUILD_DOCS"]:
+ # Build the whole documentation (Base + API) by default
+ cmake_cmd.append("-DFULLDOCSBUILD=1")
+
+ if OPTION["DOC_BUILD_ONLINE"]:
+ log.info("Output format will be HTML")
+ cmake_cmd.append("-DDOC_OUTPUT_FORMAT=html")
+ else:
+ log.info("Output format will be qthelp")
+ cmake_cmd.append("-DDOC_OUTPUT_FORMAT=qthelp")
+ else:
+ cmake_cmd.append("-DBUILD_DOCS=no")
+ if OPTION["DOC_BUILD_ONLINE"]:
+ log.info("Warning: Documentation build is disabled, "
+ "however --doc-build-online was passed. "
+ "Use '--build-docs' to enable the documentation build")
+
+ if OPTION["PYSIDE_NUMPY_SUPPORT"]:
+ log.info("Warning: '--pyside-numpy-support' is deprecated and will be removed. "
+ "Use --enable-numpy-support/--disable-numpy-support.")
+
+ target_qt_prefix_path = self.qtinfo.prefix_dir
+ cmake_cmd.append(f"-DQFP_QT_TARGET_PATH={target_qt_prefix_path}")
+ if self.qt_host_path:
+ cmake_cmd.append(f"-DQFP_QT_HOST_PATH={self.qt_host_path}")
+
+ if self.is_cross_compile and (not OPTION["SHIBOKEN_HOST_PATH"]
+ or not Path(OPTION["SHIBOKEN_HOST_PATH"]).exists()):
+ raise SetupError("Please specify the location of host shiboken tools via "
+ "--shiboken-host-path=")
+
+ if self.shiboken_host_path:
+ cmake_cmd.append(f"-DQFP_SHIBOKEN_HOST_PATH={self.shiboken_host_path}")
+
+ if self.shiboken_target_path:
+ cmake_cmd.append(f"-DQFP_SHIBOKEN_TARGET_PATH={self.shiboken_target_path}")
+ elif self.cmake_toolchain_file and not extension.lower() == SHIBOKEN:
+ # Need to tell where to find target shiboken when
+ # cross-compiling pyside.
+ cmake_cmd.append(f"-DQFP_SHIBOKEN_TARGET_PATH={self.install_dir}")
+
+ if self.cmake_toolchain_file:
+ cmake_cmd.append(f"-DCMAKE_TOOLCHAIN_FILE={self.cmake_toolchain_file}")
+
if not OPTION["SKIP_CMAKE"]:
- log.info("Configuring module {} ({})...".format(extension, module_src_dir))
+ log.info(f"Configuring module {extension} ({module_src_dir})...")
if run_process(cmake_cmd) != 0:
- raise DistutilsSetupError("Error configuring {}".format(extension))
+ raise SetupError(f"Error configuring {extension}")
else:
- log.info("Reusing old configuration for module {} ({})...".format(
- extension, module_src_dir))
+ log.info(f"Reusing old configuration for module {extension} ({module_src_dir})...")
- log.info("Compiling module {}...".format(extension))
- cmd_make = [self.make_path]
+ log.info(f"-- Compiling module {extension}...")
+ cmd_make = [str(self.make_path)]
if OPTION["JOBS"]:
cmd_make.append(OPTION["JOBS"])
+ if OPTION["LOG_LEVEL"] == LogLevel.VERBOSE and self.make_generator == "Ninja":
+ cmd_make.append("-v")
if run_process(cmd_make) != 0:
- raise DistutilsSetupError("Error compiling {}".format(extension))
-
- if not OPTION["SKIP_DOCS"]:
- if extension.lower() == "shiboken2":
- try:
- # Check if sphinx is installed to proceed.
- import sphinx
-
+ raise SetupError(f"Error compiling {extension}")
+
+ if sys.version_info == (3, 6) and sys.platform == "darwin":
+ # Python 3.6 has a Sphinx problem because of docutils 0.17 .
+ # Instead of pinning v0.16, setting the default encoding fixes that.
+ # Since other platforms are not affected, we restrict this to macOS.
+ if "UTF-8" not in os.environ.get("LC_ALL", ""):
+ os.environ["LC_ALL"] = "en_US.UTF-8"
+
+ if OPTION["BUILD_DOCS"]:
+ if extension.lower() == SHIBOKEN:
+ found = importlib.util.find_spec("sphinx")
+ if found:
log.info("Generating Shiboken documentation")
- if run_process([self.make_path, "doc"]) != 0:
- raise DistutilsSetupError("Error generating documentation "
- "for {}".format(extension))
- except ImportError:
+ make_doc_cmd = [str(self.make_path), "doc"]
+ if OPTION["LOG_LEVEL"] == LogLevel.VERBOSE and self.make_generator == "Ninja":
+ make_doc_cmd.append("-v")
+ if run_process(make_doc_cmd) != 0:
+ raise SetupError(f"Error generating documentation for {extension}")
+ else:
log.info("Sphinx not found, skipping documentation build")
else:
- log.info("Skipped documentation generation")
+ log.info("-- Skipped documentation generation. Enable with '--build-docs'")
+ cmake_cmd.append("-DBUILD_DOCS=no")
if not OPTION["SKIP_MAKE_INSTALL"]:
- log.info("Installing module {}...".format(extension))
+ log.info(f"Installing module {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
@@ -1100,25 +876,24 @@ class PysideBuild(_build):
time.sleep(1)
# ninja: error: unknown target 'install/fast'
target = 'install/fast' if self.make_generator != 'Ninja' else 'install'
- if run_process([self.make_path, target]) != 0:
- raise DistutilsSetupError("Error pseudo installing {}".format(
- extension))
+ if run_process([str(self.make_path), target]) != 0:
+ raise SetupError(f"Error pseudo installing {extension}")
else:
- log.info("Skipped installing module {}".format(extension))
+ log.info(f"Skipped installing module {extension}")
os.chdir(self.script_dir)
def prepare_packages(self):
"""
This will copy all relevant files from the various locations in the "cmake install dir",
- to the setup tools build dir (which is read from self.build_lib provided by distutils).
+ to the setup tools build dir (which is read from self.build_lib provided by setuptools).
After that setuptools.command.build_py is smart enough to copy everything
from the build dir to the install dir (the virtualenv site-packages for example).
"""
try:
- log.info("\nPreparing setup tools build directory.\n")
- vars = {
+ log.info("Preparing setup tools build directory.")
+ _vars = {
"site_packages_dir": self.site_packages_dir,
"sources_dir": self.sources_dir,
"install_dir": self.install_dir,
@@ -1131,42 +906,61 @@ class PysideBuild(_build):
"py_version": self.py_version,
"qt_version": self.qtinfo.version,
"qt_bin_dir": self.qtinfo.bins_dir,
+ "qt_data_dir": self.qtinfo.data_dir,
"qt_doc_dir": self.qtinfo.docs_dir,
"qt_lib_dir": self.qtinfo.libs_dir,
+ "qt_module_json_files_dir": self.qtinfo.module_json_files_dir,
+ "qt_metatypes_dir": self.qtinfo.metatypes_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,
+
+ # TODO: This is currently None when cross-compiling
+ # There doesn't seem to be any place where we can query
+ # it. Fortunately it's currently only used when
+ # packaging Windows vcredist.
"target_arch": self.py_arch,
}
# Needed for correct file installation in generator build
# case.
if config.is_internal_shiboken_generator_build():
- vars['cmake_package_name'] = config.shiboken_module_option_name
+ _vars['cmake_package_name'] = config.shiboken_module_option_name
os.chdir(self.script_dir)
+ # Clean up the previous st_build_dir before files are copied
+ # into it again. That's the because the same dir is used
+ # when copying the files for each of the sub-projects and
+ # we don't want to accidentally install shiboken files
+ # as part of pyside-tools package.
+ if self.st_build_dir.is_dir():
+ log.info(f"Removing {self.st_build_dir}")
+ try:
+ remove_tree(self.st_build_dir)
+ except Exception as e:
+ log.warning(f'problem removing "{self.st_build_dir}"')
+ log.warning(f'ignored error: {e}')
+
if sys.platform == "win32":
- vars['dbg_postfix'] = OPTION["DEBUG"] and "_d" or ""
- return prepare_packages_win32(self, vars)
+ _vars['dbg_postfix'] = OPTION["DEBUG"] and "_d" or ""
+ return prepare_packages_win32(self, _vars)
else:
- return prepare_packages_posix(self, vars)
+ return prepare_packages_posix(self, _vars, self.is_cross_compile)
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
+ return Path(f"{self.qtinfo.headers_dir}/../lib/QtCore.framework").is_dir()
- def get_built_pyside_config(self, vars):
+ def get_built_pyside_config(self, _vars):
# Get config that contains list of built modules, and
# SOVERSIONs of the built libraries.
- st_build_dir = vars['st_build_dir']
- config_path = os.path.join(st_build_dir, config.package_name(), "_config.py")
+ st_build_dir = Path(_vars['st_build_dir'])
+ config_path = st_build_dir / config.package_name() / "_config.py"
temp_config = get_python_dict(config_path)
if 'built_modules' not in temp_config:
temp_config['built_modules'] = []
@@ -1179,16 +973,16 @@ class PysideBuild(_build):
def prepare_standalone_clang(self, is_win=False):
"""
- Copies the libclang library to the shiboken2-generator
+ Copies the libclang library to the shiboken6-generator
package so that the shiboken executable works.
"""
log.info('Finding path to the libclang shared library.')
cmake_cmd = [
- OPTION["CMAKE"],
+ str(OPTION["CMAKE"]),
"-L", # Lists variables
"-N", # Just inspects the cache (faster)
- "--build", # Specifies the build dir
- self.shiboken_build_dir
+ "-B", # Specifies the build dir
+ str(self.shiboken_build_dir)
]
out = run_process_output(cmake_cmd)
lines = [s.strip() for s in out]
@@ -1209,11 +1003,12 @@ class PysideBuild(_build):
# 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)
+ clang_lib_path = Path(re.sub(r'lib/libclang.lib$',
+ 'bin/libclang.dll',
+ clang_lib_path))
else:
- # shiboken2 links against libclang.so.6 or a similarly
+ clang_lib_path = Path(clang_lib_path)
+ # shiboken6 links against libclang.so.6 or a similarly
# named library.
# If the linked against library is a symlink, resolve
# the symlink once (but not all the way to the real
@@ -1224,26 +1019,26 @@ class PysideBuild(_build):
# E.g. On Linux libclang.so -> libclang.so.6 ->
# libclang.so.6.0.
# "libclang.so.6" is the name we want for the copied file.
- if os.path.islink(clang_lib_path):
- link_target = os.readlink(clang_lib_path)
- if os.path.isabs(link_target):
+ if clang_lib_path.is_symlink():
+ link_target = Path(os.readlink(clang_lib_path))
+ if link_target.is_absolute():
clang_lib_path = link_target
else:
# link_target is relative, transform to absolute.
- clang_lib_path = os.path.join(os.path.dirname(clang_lib_path), link_target)
- clang_lib_path = os.path.abspath(clang_lib_path)
+ clang_lib_path = clang_lib_path.parent / link_target
+ clang_lib_path = clang_lib_path.resolve()
# The destination will be the shiboken package folder.
- vars = {}
- vars['st_build_dir'] = self.st_build_dir
- vars['st_package_name'] = config.package_name()
- destination_dir = "{st_build_dir}/{st_package_name}".format(**vars)
+ _vars = {}
+ _vars['st_build_dir'] = self.st_build_dir
+ _vars['st_package_name'] = config.package_name()
+ destination_dir = Path("{st_build_dir}/{st_package_name}".format(**_vars))
- if os.path.exists(clang_lib_path):
- basename = os.path.basename(clang_lib_path)
- log.info('Copying libclang shared library {} to the package folder as {}.'.format(
- clang_lib_path, basename))
- destination_path = os.path.join(destination_dir, basename)
+ if clang_lib_path.exists():
+ basename = clang_lib_path.name
+ log.info(f"Copying libclang shared library {clang_lib_path} to the package "
+ f"folder as {basename}.")
+ destination_path = destination_dir / basename
# Need to modify permissions in case file is not writable
# (a reinstall would cause a permission denied error).
@@ -1253,12 +1048,51 @@ class PysideBuild(_build):
make_writable_by_owner=True)
else:
raise RuntimeError("Error copying libclang library "
- "from {} to {}. ".format(clang_lib_path, destination_dir))
-
- def update_rpath(self, package_path, executables):
+ f"from {clang_lib_path} to {destination_dir}. ")
+
+ def get_shared_library_filters(self):
+ unix_filters = ["*.so", "*.so.*"]
+ darwin_filters = ["*.so", "*.dylib"]
+ filters = []
+ if self.is_cross_compile:
+ if 'darwin' in self.plat_name or 'macos' in self.plat_name:
+ filters = darwin_filters
+ elif 'linux' in self.plat_name or 'android' in self.plat_name:
+ filters = unix_filters
+ else:
+ log.warning(f"No shared library filters found for platform {self.plat_name}. "
+ f"The package might miss Qt libraries and plugins.")
+ else:
+ if sys.platform == 'darwin':
+ filters = darwin_filters
+ else:
+ filters = unix_filters
+ return filters
+
+ def _find_shared_libraries(self, path, recursive=False):
+ """Helper to find shared libraries in a path."""
+ result = set()
+ for filter in self.get_shared_library_filters():
+ glob_pattern = f"**/{filter}" if recursive else filter
+ for library in path.glob(glob_pattern):
+ result.add(library)
+ return list(result)
+
+ def package_libraries(self, package_path):
+ """Returns the libraries of the Python module"""
+ return self._find_shared_libraries(package_path)
+
+ def get_shared_libraries_in_path_recursively(self, initial_path):
+ """Returns shared library plugins in given path (collected
+ recursively)"""
+ return self._find_shared_libraries(initial_path, recursive=True)
+
+ def update_rpath(self, executables, libexec=False, message=None):
+ ROOT = '@loader_path' if sys.platform == 'darwin' else '$ORIGIN'
+ QT_PATH = '/../lib' if libexec else '/Qt/lib'
+
+ message = "Patched rpath to '$ORIGIN/' in"
if sys.platform.startswith('linux'):
- pyside_libs = [lib for lib in os.listdir(
- package_path) if filter_match(lib, ["*.so", "*.so.*"])]
def rpath_cmd(srcpath):
final_rpath = ''
@@ -1271,14 +1105,13 @@ class PysideBuild(_build):
# installed qt lib directory.
final_rpath = self.qtinfo.libs_dir
if OPTION["STANDALONE"]:
- final_rpath = "$ORIGIN/Qt/lib"
+ final_rpath = f'{ROOT}{QT_PATH}'
override = OPTION["STANDALONE"]
linux_fix_rpaths_for_library(self._patchelf_path, srcpath, final_rpath,
override=override)
elif sys.platform == 'darwin':
- pyside_libs = [lib for lib in os.listdir(
- package_path) if filter_match(lib, ["*.so", "*.dylib"])]
+ message = "Updated rpath in"
def rpath_cmd(srcpath):
final_rpath = ''
@@ -1288,26 +1121,186 @@ class PysideBuild(_build):
final_rpath = OPTION["RPATH_VALUES"]
else:
if OPTION["STANDALONE"]:
- final_rpath = "@loader_path/Qt/lib"
+ final_rpath = f'{ROOT}{QT_PATH}'
else:
final_rpath = self.qtinfo.libs_dir
macos_fix_rpaths_for_library(srcpath, final_rpath)
else:
- raise RuntimeError('Not configured for platform {}'.format(sys.platform))
+ raise RuntimeError(f"Not configured for platform {sys.platform}")
+
+ # Update rpath
+ for executable in executables:
+ if executable.is_dir() or executable.is_symlink():
+ continue
+ if not executable.exists():
+ continue
+ rpath_cmd(executable)
+ log.debug(f"{message} {executable}.")
+
+ def update_rpath_for_linux_plugins(
+ self,
+ plugin_paths,
+ qt_lib_dir=None,
+ is_qml_plugin=False):
+
+ # If the linux sysroot (where the plugins are copied from)
+ # is from a mainline distribution, it might have a different
+ # directory layout than then one we expect to have in the
+ # wheel.
+ # We have to ensure that any plugins copied have rpath
+ # values that can find Qt libs in the newly assembled wheel
+ # dir layout.
+ if not (self.is_cross_compile and sys.platform.startswith('linux') and self.standalone):
+ return
+
+ log.info("Patching rpath for Qt and QML plugins.")
+ for plugin in plugin_paths:
+ if plugin.is_dir() or plugin.is_symlink():
+ continue
+ if not plugin.exists():
+ continue
- pyside_libs.extend(executables)
+ if is_qml_plugin:
+ plugin_dir = plugin.parent
+ # FIXME: there is no os.path.relpath equivalent on pathlib.
+ # The Path.relative_to is not equivalent and raises ValueError when the paths
+ # are not subpaths, so it doesn't generate "../../something".
+ rel_path_from_qml_plugin_qt_lib_dir = os.path.relpath(qt_lib_dir, plugin_dir)
+ rpath_value = Path("$ORIGIN") / rel_path_from_qml_plugin_qt_lib_dir
+ else:
+ rpath_value = "$ORIGIN/../../lib"
+
+ linux_fix_rpaths_for_library(self._patchelf_path, plugin, rpath_value,
+ override=True)
+ log.debug(f"Patched rpath to '{rpath_value}' in {plugin}.")
- # 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):
+ def update_rpath_for_linux_qt_libraries(self, qt_lib_dir):
+ # Ensure that Qt libs and ICU libs have $ORIGIN in their rpath.
+ # Especially important for ICU lib, so that they don't
+ # accidentally load dependencies from the system.
+ if not (self.is_cross_compile and sys.platform.startswith('linux') and self.standalone):
+ return
+
+ qt_lib_dir = Path(qt_lib_dir)
+ rpath_value = "$ORIGIN"
+ log.info(f"Patching rpath for Qt and ICU libraries in {qt_lib_dir}.")
+ for library in self.package_libraries(qt_lib_dir):
+ if library.is_dir() or library.is_symlink():
continue
- if not os.path.exists(srcpath):
+ if not library.exists():
continue
- rpath_cmd(srcpath)
- log.info("Patched rpath to '$ORIGIN/' (Linux) or "
- "updated rpath (OS/X) in {}.".format(srcpath))
+
+ linux_fix_rpaths_for_library(self._patchelf_path, library, rpath_value, override=True)
+ log.debug(f"Patched rpath to '{rpath_value}' in {library}.")
+
+
+class PysideBaseDocs(Command, CommandMixin):
+ description = "Build the base documentation only"
+ user_options = CommandMixin.mixin_user_options
+
+ def __init__(self, *args, **kwargs):
+ self.command_name = "build_base_docs"
+ Command.__init__(self, *args, **kwargs)
+ CommandMixin.__init__(self)
+
+ def initialize_options(self):
+ log.info("-- This build process will not include the API documentation. "
+ "API documentation requires a full build of pyside/shiboken.")
+ self.skip = False
+ if config.is_internal_shiboken_generator_build():
+ self.skip = True
+ if not self.skip:
+ self.name = config.package_name().lower()
+ self.doc_dir = config.setup_script_dir / "sources" / self.name / "doc"
+ # Check if sphinx is installed to proceed.
+ found = importlib.util.find_spec("sphinx")
+ self.html_dir = Path("html")
+ if found:
+ if self.name == SHIBOKEN:
+ # Delete the 'html' directory since new docs will be generated anyway
+ if self.html_dir.is_dir():
+ rmtree(self.html_dir)
+ log.info("-- Deleted old html directory")
+ log.info("-- Generating Shiboken documentation")
+ log.info(f"-- Documentation directory: 'html/{PYSIDE}/{SHIBOKEN}/'")
+ elif self.name == PYSIDE:
+ log.info("-- Generating PySide documentation")
+ log.info(f"-- Documentation directory: 'html/{PYSIDE}/'")
+ else:
+ raise SetupError("Sphinx not found - aborting")
+
+ # creating directories html/pyside6/shiboken6
+ try:
+ if not self.html_dir.is_dir():
+ self.html_dir.mkdir(parents=True)
+ if self.name == SHIBOKEN:
+ out_pyside = self.html_dir / PYSIDE
+ if not out_pyside.is_dir():
+ out_pyside.mkdir(parents=True)
+ out_shiboken = out_pyside / SHIBOKEN
+ if not out_shiboken.is_dir():
+ out_shiboken.mkdir(parents=True)
+ self.out_dir = out_shiboken
+ # We know that on the shiboken step, we already created the
+ # 'pyside6' directory
+ elif self.name == PYSIDE:
+ self.out_dir = self.html_dir / PYSIDE
+ except (PermissionError, FileExistsError):
+ raise SetupError(f"Error while creating directories for {self.doc_dir}")
+
+ def run(self):
+ if not self.skip:
+ cmake_cmd = [
+ str(OPTION["CMAKE"]),
+ "-S", str(self.doc_dir),
+ "-B", str(self.out_dir),
+ "-DDOC_OUTPUT_FORMAT=html",
+ "-DFULLDOCSBUILD=0",
+ ]
+
+ cmake_quiet_build = 1
+ cmake_message_log_level = "STATUS"
+
+ # Define log level
+ if OPTION["LOG_LEVEL"] == LogLevel.VERBOSE:
+ cmake_quiet_build = 0
+ cmake_message_log_level = "VERBOSE"
+ elif OPTION["LOG_LEVEL"] == LogLevel.QUIET:
+ cmake_message_log_level = "ERROR"
+
+ cmake_cmd.append(f"-DQUIET_BUILD={cmake_quiet_build}")
+ cmake_cmd.append(f"-DCMAKE_MESSAGE_LOG_LEVEL={cmake_message_log_level}")
+
+ if run_process(cmake_cmd) != 0:
+ raise SetupError(f"Error running CMake for {self.doc_dir}")
+
+ if self.name == PYSIDE:
+ self.sphinx_src = self.out_dir / "base"
+ example_gallery = config.setup_script_dir / "tools" / "example_gallery" / "main.py"
+ assert example_gallery.is_file()
+ example_gallery_cmd = [sys.executable, os.fspath(example_gallery)]
+ if OPTION["LOG_LEVEL"] == LogLevel.QUIET:
+ example_gallery_cmd.append("--quiet")
+ qt_src_dir = OPTION['QT_SRC']
+ if qt_src_dir:
+ example_gallery_cmd.extend(["--qt-src-dir", qt_src_dir])
+ if run_process(example_gallery_cmd) != 0:
+ raise SetupError(f"Error running example gallery for {self.doc_dir}")
+ elif self.name == SHIBOKEN:
+ self.sphinx_src = self.out_dir
+
+ sphinx_cmd = ["sphinx-build", "-b", "html", "-j", "auto", "-c",
+ str(self.sphinx_src), str(self.doc_dir),
+ str(self.out_dir)]
+ if run_process(sphinx_cmd) != 0:
+ raise SetupError(f"Error running CMake for {self.doc_dir}")
+ # Last message
+ if not self.skip and self.name == PYSIDE:
+ log.info(f"-- The documentation was built. Check html/{PYSIDE}/index.html")
+
+ def finalize_options(self):
+ CommandMixin.mixin_finalize_options(self)
cmd_class_dict = {
@@ -1317,14 +1310,10 @@ cmd_class_dict = {
'bdist_egg': PysideBdistEgg,
'develop': PysideDevelop,
'install': PysideInstall,
- 'install_lib': PysideInstallLib
+ 'install_lib': PysideInstallLib,
+ 'build_base_docs': PysideBaseDocs,
}
if wheel_module_exists:
- params = {}
- params['qt_version'] = get_qt_version()
- params['package_version'] = get_package_version()
- if sys.platform == 'darwin':
- params['macos_plat_name'] = PysideBuild.macos_plat_name()
- pyside_bdist_wheel = get_bdist_wheel_override(params)
+ pyside_bdist_wheel = get_bdist_wheel_override()
if pyside_bdist_wheel:
cmd_class_dict['bdist_wheel'] = pyside_bdist_wheel
diff --git a/build_scripts/options.py b/build_scripts/options.py
index 4229a2c10..806d4a8a3 100644
--- a/build_scripts/options.py
+++ b/build_scripts/options.py
@@ -1,57 +1,42 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from __future__ import print_function
+# Copyright (C) 2018 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+from setuptools import Command
+
import sys
-import os
-import warnings
+import logging
+from pathlib import Path
+
+from .log import log, LogLevel
+from .qtinfo import QtInfo
+from .utils import memoize, which
+
+_AVAILABLE_MKSPECS = ["ninja", "msvc", "mingw"] if sys.platform == "win32" else ["ninja", "make"]
+
+
+# Global options not which are not part of the commands
+ADDITIONAL_OPTIONS = """
+Additional options:
+ --limited-api Use Limited API [yes/no]
+ --macos-use-libc++ Use libc++ on macOS
+ --snapshot-build Snapshot build
+ --package-timestamp Package Timestamp
+ --cmake-toolchain-file Path to CMake toolchain to enable cross-compiling
+ --shiboken-host-path Path to host shiboken package when cross-compiling
+ --qt-host-path Path to host Qt installation when cross-compiling
+ --disable-pyi Disable .pyi file generation
+"""
def _warn_multiple_option(option):
- warnings.warn('Option "{}" occurs multiple times on the command line.'.format(option))
+ log.warning(f'Option "{option}" occurs multiple times on the command line.')
def _warn_deprecated_option(option, replacement=None):
- w = 'Option "{}" is deprecated and may be removed in a future release.'.format(option)
+ w = f'Option "{option}" is deprecated and may be removed in a future release.'
if replacement:
- w = '{}\nUse "{}" instead.'.format(w, replacement)
- warnings.warn(w)
+ w = f'{w}\nUse "{replacement}" instead.'
+ log.warning(w)
class Options(object):
@@ -63,12 +48,12 @@ class Options(object):
def has_option(self, name, remove=True):
""" Returns True if argument '--name' was passed on the command
line. """
- option = '--{}'.format(name)
+ option = f"--{name}"
count = sys.argv.count(option)
remove_count = count
if not remove and count > 0:
remove_count -= 1
- for i in range(remove_count):
+ for _ in range(remove_count):
sys.argv.remove(option)
if count > 1:
_warn_multiple_option(option)
@@ -76,11 +61,9 @@ class Options(object):
def option_value(self, name, short_option_name=None, remove=True):
"""
- Returns the value of a command line option or environment
- variable.
+ Returns the value of a command line option.
- :param name: The name of the command line option or environment
- variable.
+ :param name: The name of the command line option.
:param remove: Whether the option and its value should be
removed from sys.argv. Useful when there's a need to query for
@@ -88,9 +71,10 @@ class Options(object):
:return: Either the option value or None.
"""
- option = '--' + name
- short_option = '-' + short_option_name if short_option_name else None
- single_option_prefix = option + '='
+
+ option = f"--{name}"
+ short_option = f"-{short_option_name}" if short_option_name else None
+ single_option_prefix = f"{option}="
value = None
for index in reversed(range(len(sys.argv))):
arg = sys.argv[index]
@@ -99,7 +83,7 @@ class Options(object):
_warn_multiple_option(option)
else:
if index + 1 >= len(sys.argv):
- raise RuntimeError("The option {} requires a value".format(option))
+ raise RuntimeError(f"The option {option} requires a value")
value = sys.argv[index + 1]
if remove:
@@ -114,9 +98,6 @@ class Options(object):
if remove:
sys.argv[index:index + 1] = []
- if value is None:
- value = os.getenv(name.upper().replace('-', '_'))
-
self.dict[name] = value
return value
@@ -132,58 +113,486 @@ def option_value(*args, **kwargs):
return options.option_value(*args, **kwargs)
-# Declare options
+def _jobs_option_value():
+ """Option value for parallel builds."""
+ value = option_value('parallel', short_option_name='j')
+ if value:
+ return f"-j{value}" if not value.startswith('-j') else value
+ return ''
+
+
+def find_qtpaths():
+ # for these command --qtpaths should not be required
+ no_qtpaths_commands = ["--help", "--help-commands", "--qt-target-path", "build_base_docs"]
+
+ for no_qtpaths_command in no_qtpaths_commands:
+ if any(no_qtpaths_command in argument for argument in sys.argv):
+ return None
+
+ qtpaths = option_value("qtpaths")
+ if qtpaths:
+ return qtpaths
+
+ # if qtpaths is not given as cli option, try to find it in PATH
+ qtpaths = which("qtpaths6")
+ if qtpaths:
+ return str(qtpaths.resolve())
+
+ qtpaths = which("qtpaths")
+ if qtpaths:
+ return str(qtpaths.resolve())
+
+ return qtpaths
+
+
+# Declare options which need to be known when instantiating the setuptools
+# commands or even earlier during SetupRunner.run().
OPTION = {
"BUILD_TYPE": option_value("build-type"),
"INTERNAL_BUILD_TYPE": option_value("internal-build-type"),
- "DEBUG": has_option("debug"),
- "RELWITHDEBINFO": has_option('relwithdebinfo'),
- "QMAKE": option_value("qmake"),
- "QT_VERSION": option_value("qt"),
- "CMAKE": option_value("cmake"),
- "OPENSSL": option_value("openssl"),
- "SHIBOKEN_CONFIG_DIR": option_value("shiboken-config-dir"),
- "ONLYPACKAGE": has_option("only-package"),
- "STANDALONE": has_option("standalone"),
- "MAKESPEC": option_value("make-spec"),
- "IGNOREGIT": has_option("ignore-git"),
- # don't generate documentation
- "SKIP_DOCS": has_option("skip-docs"),
- # don't include pyside2-examples
- "NOEXAMPLES": has_option("no-examples"),
# number of parallel build jobs
- "JOBS": option_value('parallel', short_option_name='j'),
+ "JOBS": _jobs_option_value(),
# Legacy, not used any more.
"JOM": has_option('jom'),
- # Do not use jom instead of nmake with msvc
- "NO_JOM": has_option('no-jom'),
- "BUILDTESTS": has_option("build-tests"),
- "MACOS_ARCH": option_value("macos-arch"),
"MACOS_USE_LIBCPP": has_option("macos-use-libc++"),
- "MACOS_SYSROOT": option_value("macos-sysroot"),
- "MACOS_DEPLOYMENT_TARGET": option_value("macos-deployment-target"),
- "XVFB": has_option("use-xvfb"),
- "REUSE_BUILD": has_option("reuse-build"),
- "SKIP_CMAKE": has_option("skip-cmake"),
- "SKIP_MAKE_INSTALL": has_option("skip-make-install"),
- "SKIP_PACKAGING": has_option("skip-packaging"),
- "SKIP_MODULES": option_value("skip-modules"),
- "MODULE_SUBSET": option_value("module-subset"),
- "RPATH_VALUES": option_value("rpath"),
- "QT_CONF_PREFIX": option_value("qt-conf-prefix"),
- "QT_SRC": option_value("qt-src-dir"),
- "QUIET": has_option('quiet', remove=False),
- "VERBOSE_BUILD": has_option("verbose-build"),
- "SANITIZE_ADDRESS": has_option("sanitize-address"),
+ "LOG_LEVEL": option_value("log-level", remove=False),
+ "QUIET": has_option('quiet'),
+ "VERBOSE_BUILD": has_option('verbose-build'),
"SNAPSHOT_BUILD": has_option("snapshot-build"),
"LIMITED_API": option_value("limited-api"),
+ "DISABLE_PYI": has_option("disable-pyi"),
"PACKAGE_TIMESTAMP": option_value("package-timestamp"),
- "SHORTER_PATHS": has_option("shorter-paths"),
- # This is used automatically by distutils.command.install object, to
+ # This is used automatically by setuptools.command.install object, to
# specify the final installation location.
"FINAL_INSTALL_PREFIX": option_value("prefix", remove=False),
+ "CMAKE_TOOLCHAIN_FILE": option_value("cmake-toolchain-file"),
+ "SHIBOKEN_HOST_PATH": option_value("shiboken-host-path"),
+ "SHIBOKEN_HOST_PATH_QUERY_FILE": option_value("internal-shiboken-host-path-query-file"),
+ "QT_HOST_PATH": option_value("qt-host-path"),
+ # This is used to identify the template for doc builds
+ "QTPATHS": find_qtpaths()
+ # This is an optional command line option. If --qtpaths is not provided via command-line,
+ # then qtpaths is checked inside PATH variable
}
+
_deprecated_option_jobs = option_value('jobs')
if _deprecated_option_jobs:
_warn_deprecated_option('jobs', 'parallel')
OPTION["JOBS"] = _deprecated_option_jobs
+
+
+class CommandMixin(object):
+ """Mixin for the setuptools build/install commands handling the options."""
+
+ _static_class_finalized_once = False
+
+ mixin_user_options = [
+ ('avoid-protected-hack', None, 'Force --avoid-protected-hack'),
+ ('debug', None, 'Build with debug information'),
+ ('relwithdebinfo', None, 'Build in release mode with debug information'),
+ ('only-package', None, 'Package only'),
+ ('no-strip', None, 'Do not strip package libraries (release mode)'),
+ ('standalone', None, 'Standalone build'),
+ ('ignore-git', None, 'Do update subrepositories'),
+ ('skip-docs', None, 'Skip documentation build (deprecated)'),
+ ('build-docs', None, 'Build the API documentation'),
+ ('no-jom', None, 'Do not use jom (MSVC)'),
+ ('build-tests', None, 'Build tests'),
+ ('use-xvfb', None, 'Use Xvfb for testing'),
+ ('reuse-build', None, 'Reuse existing build'),
+ ('compiler-launcher=', None, 'Use a compiler launcher like ccache or sccache for builds'),
+ ('skip-cmake', None, 'Skip CMake step'),
+ ('skip-make-install', None, 'Skip install step'),
+ ('skip-packaging', None, 'Skip packaging step'),
+ ('log-level=', None, 'Log level of the build.'),
+ ('verbose-build', None, 'Verbose build'),
+ ('quiet', None, 'Quiet build'),
+ ('sanitize-address', None, 'Build with address sanitizer'),
+ ('shorter-paths', None, 'Use shorter paths'),
+ ('doc-build-online', None, 'Build online documentation'),
+ ('qtpaths=', None, 'Path to qtpaths'),
+ ('qmake=', None, 'Path to qmake (deprecated, use qtpaths)'),
+ ('qt=', None, 'Qt version'),
+ ('qt-target-path=', None,
+ 'Path to device Qt installation (use Qt libs when cross-compiling)'),
+ ('cmake=', None, 'Path to CMake'),
+ ('openssl=', None, 'Path to OpenSSL libraries'),
+
+ # FIXME: Deprecated in favor of shiboken-target-path
+ ('shiboken-config-dir=', None, 'shiboken configuration directory'),
+
+ ('shiboken-target-path=', None, 'Path to target shiboken package'),
+ ('python-target-path=', None, 'Path to target Python installation / prefix'),
+ ('make-spec=', None, 'Qt make-spec'),
+ ('macos-arch=', None, 'macOS architecture'),
+ ('macos-sysroot=', None, 'macOS sysroot'),
+ ('macos-deployment-target=', None, 'macOS deployment target'),
+ ('skip-modules=', None, 'Qt modules to be skipped'),
+ ('module-subset=', None, 'Qt modules to be built'),
+ ('rpath=', None, 'RPATH'),
+ ('qt-conf-prefix=', None, 'Qt configuration prefix'),
+ ('qt-src-dir=', None, 'Qt source directory'),
+ ('no-qt-tools', None, 'Do not copy the Qt tools'),
+ ('no-size-optimization', None, 'Turn off size optimization for PySide6 binaries'),
+ # Default is auto-detected by PysideBuild._enable_numpy()
+ ('pyside-numpy-support', None, 'libpyside: Add numpy support (deprecated)'),
+ ('enable-numpy-support', None, 'Enable numpy support'),
+ ('disable-numpy-support', None, 'Disable numpy support'),
+ ('internal-cmake-install-dir-query-file-path=', None,
+ 'Path to file where the CMake install path of the project will be saved'),
+
+ # We redeclare plat-name as an option so it's recognized by the
+ # install command and doesn't throw an error.
+ ('plat-name=', None, 'The platform name for which we are cross-compiling'),
+ ('unity', None, 'Use CMake UNITY_BUILD_MODE (obsolete)'),
+ ('no-unity', None, 'Disable CMake UNITY_BUILD_MODE'),
+ ('unity-build-batch-size=', None, 'Value of CMAKE_UNITY_BUILD_BATCH_SIZE')
+ ]
+
+ def __init__(self):
+ self.avoid_protected_hack = False
+ self.debug = False
+ self.relwithdebinfo = False
+ self.no_strip = False
+ self.only_package = False
+ self.standalone = False
+ self.ignore_git = False
+ self.skip_docs = False
+ self.build_docs = False
+ self.no_jom = False
+ self.build_tests = False
+ self.use_xvfb = False
+ self.reuse_build = False
+ self.compiler_launcher = None
+ self.skip_cmake = False
+ self.skip_make_install = False
+ self.skip_packaging = False
+ self.log_level = "info"
+ self.verbose_build = False
+ self.sanitize_address = False
+ self.snapshot_build = False
+ self.shorter_paths = False
+ self.doc_build_online = False
+ self.qtpaths = None
+ self.qmake = None
+ self.has_qmake_option = False
+ self.qt = '5'
+ self.qt_host_path = None
+ self.qt_target_path = None
+ self.cmake = None
+ self.openssl = None
+ self.shiboken_config_dir = None
+ self.shiboken_host_path = None
+ self.shiboken_host_path_query_file = None
+ self.shiboken_target_path = None
+ self.python_target_path = None
+ self.is_cross_compile = False
+ self.cmake_toolchain_file = None
+ self.make_spec = None
+ self.macos_arch = None
+ self.macos_sysroot = None
+ self.macos_deployment_target = None
+ self.skip_modules = None
+ self.module_subset = None
+ self.rpath = None
+ self.qt_conf_prefix = None
+ self.qt_src_dir = None
+ self.no_qt_tools = False
+ self.no_size_optimization = False
+ self.pyside_numpy_support = False
+ self.enable_numpy_support = False
+ self.disable_numpy_support = False
+ self.plat_name = None
+ self.internal_cmake_install_dir_query_file_path = None
+ self._per_command_mixin_options_finalized = False
+ self.unity = False
+ self.no_unity = False
+ self.unity_build_batch_size = "16"
+
+ # When initializing a command other than the main one (so the
+ # first one), we need to copy the user options from the main
+ # command to the new command options dict. Then
+ # Distribution.get_command_obj will pick up the copied options
+ # ensuring that all commands that inherit from
+ # the mixin, get our custom properties set by the time
+ # finalize_options is called.
+ if CommandMixin._static_class_finalized_once:
+ current_command: Command = self
+ dist = current_command.distribution
+ main_command_name = dist.commands[0]
+ main_command_opts = dist.get_option_dict(main_command_name)
+ current_command_name = current_command.get_command_name()
+ current_command_opts = dist.get_option_dict(current_command_name)
+ mixin_options_set = self.get_mixin_options_set()
+ for key, value in main_command_opts.items():
+ if key not in current_command_opts and key in mixin_options_set:
+ current_command_opts[key] = value
+
+ # qtpaths is already known before running SetupRunner
+ self.qtpaths = OPTION["QTPATHS"]
+
+ @staticmethod
+ @memoize
+ def get_mixin_options_set():
+ keys = set()
+ for (name, _, _) in CommandMixin.mixin_user_options:
+ keys.add(name.rstrip("=").replace("-", "_"))
+ return keys
+
+ def mixin_finalize_options(self):
+ # The very first we finalize options, record that.
+ if not CommandMixin._static_class_finalized_once:
+ CommandMixin._static_class_finalized_once = True
+
+ # Ensure we finalize once per command object, rather than per
+ # setup.py invocation. We want to have the option values
+ # available in all commands that derive from the mixin.
+ if not self._per_command_mixin_options_finalized:
+ self._per_command_mixin_options_finalized = True
+ self._do_finalize()
+
+ def _do_finalize(self):
+ # is_cross_compile must be set before checking for qtpaths/qmake
+ # because we DON'T want those to be found when cross compiling.
+ # Currently when cross compiling, qt-target-path MUST be used.
+ using_cmake_toolchain_file = False
+ cmake_toolchain_file = None
+ if OPTION["CMAKE_TOOLCHAIN_FILE"]:
+ self.is_cross_compile = True
+ using_cmake_toolchain_file = True
+ cmake_toolchain_file = OPTION["CMAKE_TOOLCHAIN_FILE"]
+ self.cmake_toolchain_file = cmake_toolchain_file
+
+ if not self._determine_defaults_and_check():
+ sys.exit(-1)
+ OPTION['AVOID_PROTECTED_HACK'] = self.avoid_protected_hack
+ OPTION['DEBUG'] = self.debug
+ OPTION['RELWITHDEBINFO'] = self.relwithdebinfo
+ OPTION['NO_STRIP'] = self.no_strip
+ OPTION['ONLYPACKAGE'] = self.only_package
+ OPTION['STANDALONE'] = self.standalone
+ if self.ignore_git:
+ _warn_deprecated_option('ignore_git')
+ OPTION['SKIP_DOCS'] = self.skip_docs
+ OPTION['BUILD_DOCS'] = self.build_docs
+ OPTION['BUILDTESTS'] = self.build_tests
+
+ OPTION['NO_JOM'] = self.no_jom
+ OPTION['XVFB'] = self.use_xvfb
+ OPTION['REUSE_BUILD'] = self.reuse_build
+ OPTION['COMPILER_LAUNCHER'] = self.compiler_launcher
+ OPTION['SKIP_CMAKE'] = self.skip_cmake
+ OPTION['SKIP_MAKE_INSTALL'] = self.skip_make_install
+ OPTION['SKIP_PACKAGING'] = self.skip_packaging
+ # Logging options:
+ # 'quiet' and 'verbose-build' are deprecated,
+ # log-level has higher priority when used.
+ OPTION['LOG_LEVEL'] = self.log_level
+ OPTION['VERBOSE_BUILD'] = self.verbose_build
+ # The OPTION["QUIET"] doesn't need to be initialized with a value
+ # because is an argument that it will not be removed due to being
+ # a setuptools argument as well.
+
+ # By default they are False, so we check if they changed with xor
+ if bool(OPTION["QUIET"]) != bool(OPTION["VERBOSE_BUILD"]):
+ log.warning("Using --quiet and --verbose-build is deprecated. "
+ "Please use --log-level=quiet or --log-level=verbose instead.")
+ # We assign a string value instead of the enum
+ # because is what we get from the command line.
+ # Later we assign the enum
+ if OPTION["QUIET"]:
+ OPTION["LOG_LEVEL"] = "quiet"
+ elif OPTION["VERBOSE_BUILD"]:
+ OPTION["LOG_LEVEL"] = "verbose"
+
+ if OPTION["LOG_LEVEL"] not in ("quiet", "info", "verbose"):
+ log.error(f"Invalid value for log level: '--log-level={OPTION['LOG_LEVEL']}'. "
+ "Use 'quiet', 'info', or 'verbose'.")
+ sys.exit(-1)
+ else:
+ if OPTION["LOG_LEVEL"] == "quiet":
+ OPTION["LOG_LEVEL"] = LogLevel.QUIET
+ log.setLevel(logging.ERROR)
+ elif OPTION["LOG_LEVEL"] == "info":
+ OPTION["LOG_LEVEL"] = LogLevel.INFO
+ log.setLevel(logging.INFO)
+ elif OPTION["LOG_LEVEL"] == "verbose":
+ OPTION["LOG_LEVEL"] = LogLevel.VERBOSE
+ log.setLevel(logging.DEBUG)
+
+ OPTION['SANITIZE_ADDRESS'] = self.sanitize_address
+ OPTION['SHORTER_PATHS'] = self.shorter_paths
+ OPTION['DOC_BUILD_ONLINE'] = self.doc_build_online
+ if self.unity:
+ log.warning("Using --unity no longer has any effect, "
+ "Unity build mode is now the default.")
+ OPTION['UNITY'] = not self.no_unity
+ OPTION['UNITY_BUILD_BATCH_SIZE'] = self.unity_build_batch_size
+
+ qtpaths_abs_path = None
+ if self.qtpaths and Path(self.qtpaths).exists():
+ qtpaths_abs_path = Path(self.qtpaths).resolve()
+
+ # FIXME PYSIDE7: Remove qmake handling
+ # make qtinfo.py independent of relative paths.
+ qmake_abs_path = None
+ if self.qmake:
+ qmake_abs_path = Path(self.qmake).resolve()
+ OPTION['QMAKE'] = qmake_abs_path
+ OPTION['HAS_QMAKE_OPTION'] = self.has_qmake_option
+ OPTION['QT_VERSION'] = self.qt
+ self.qt_host_path = OPTION['QT_HOST_PATH']
+ OPTION['QT_TARGET_PATH'] = self.qt_target_path
+
+ qt_target_path = self.qt_target_path
+
+ # We use the CMake project to find host Qt if neither qmake or
+ # qtpaths is available. This happens when building the host
+ # tools in the overall cross-building process.
+ use_cmake = False
+ if (using_cmake_toolchain_file or (not self.qmake
+ and not self.qtpaths and self.qt_target_path)):
+ use_cmake = True
+
+ QtInfo().setup(qtpaths_abs_path, self.cmake, qmake_abs_path,
+ self.has_qmake_option,
+ use_cmake=use_cmake,
+ qt_target_path=qt_target_path,
+ cmake_toolchain_file=cmake_toolchain_file)
+
+ if 'build_base_docs' not in sys.argv:
+ try:
+ QtInfo().prefix_dir
+ except Exception as e:
+ if not self.qt_target_path:
+ log.error(
+ "\nCould not find Qt. You can pass the --qt-target-path=<qt-dir> option "
+ "as a hint where to find Qt. Error was:\n\n\n")
+ else:
+ log.error(
+ f"\nCould not find Qt via provided option --qt-target-path={qt_target_path}"
+ "Error was:\n\n\n")
+ raise e
+
+ OPTION['CMAKE'] = self.cmake.resolve()
+ OPTION['OPENSSL'] = self.openssl
+ OPTION['SHIBOKEN_CONFIG_DIR'] = self.shiboken_config_dir
+ if self.shiboken_config_dir:
+ _warn_deprecated_option('shiboken-config-dir', 'shiboken-target-path')
+
+ self.shiboken_host_path = OPTION['SHIBOKEN_HOST_PATH']
+ self.shiboken_host_path_query_file = OPTION['SHIBOKEN_HOST_PATH_QUERY_FILE']
+
+ if not self.shiboken_host_path and self.shiboken_host_path_query_file:
+ try:
+ queried_shiboken_host_path = Path(self.shiboken_host_path_query_file).read_text()
+ self.shiboken_host_path = queried_shiboken_host_path
+ OPTION['SHIBOKEN_HOST_PATH'] = queried_shiboken_host_path
+ except Exception as e:
+ log.error(
+ f"\n Could not find shiboken host tools via the query file: "
+ f"{self.shiboken_host_path_query_file:} Error was:\n\n\n")
+ raise e
+
+ OPTION['SHIBOKEN_TARGET_PATH'] = self.shiboken_target_path
+ OPTION['PYTHON_TARGET_PATH'] = self.python_target_path
+ OPTION['MAKESPEC'] = self.make_spec
+ OPTION['MACOS_ARCH'] = self.macos_arch
+ OPTION['MACOS_SYSROOT'] = self.macos_sysroot
+ OPTION['MACOS_DEPLOYMENT_TARGET'] = self.macos_deployment_target
+ OPTION['SKIP_MODULES'] = self.skip_modules
+ OPTION['MODULE_SUBSET'] = self.module_subset
+ OPTION['RPATH_VALUES'] = self.rpath
+ OPTION['QT_CONF_PREFIX'] = self.qt_conf_prefix
+ OPTION['QT_SRC'] = self.qt_src_dir
+ OPTION['NO_QT_TOOLS'] = self.no_qt_tools
+ OPTION['NO_OVERRIDE_OPTIMIZATION_FLAGS'] = self.no_size_optimization
+ OPTION['DISABLE_NUMPY_SUPPORT'] = self.disable_numpy_support
+ OPTION['ENABLE_NUMPY_SUPPORT'] = self.enable_numpy_support
+ OPTION['PYSIDE_NUMPY_SUPPORT'] = self.pyside_numpy_support
+
+ if not self._extra_checks():
+ sys.exit(-1)
+
+ OPTION['PLAT_NAME'] = self.plat_name
+
+ def _extra_checks(self):
+ if self.is_cross_compile and not self.plat_name:
+ log.error("No value provided to --plat-name while cross-compiling.")
+ return False
+ return True
+
+ def _determine_defaults_and_check(self):
+ if not self.cmake:
+ self.cmake = Path(which("cmake"))
+ elif isinstance(self.cmake, str): # command line option
+ self.cmake = Path(self.cmake)
+ if not self.cmake:
+ log.error("cmake could not be found.")
+ return False
+ if not self.cmake.exists():
+ log.error(f"'{self.cmake}' does not exist.")
+ return False
+
+ # Setting up the Paths when passing via command line
+ if isinstance(self.qtpaths, str):
+ self.qtpaths = Path(self.qtpaths)
+ if isinstance(self.qmake, str):
+ self.qmake = Path(self.qmake)
+ if self.qt_target_path and isinstance(self.qt_target_path, str):
+ self.qt_target_path = Path(self.qt_target_path)
+
+ # When cross-compiling, we only accept the qt-target-path
+ # option and don't rely on auto-searching in PATH or the other
+ # qtpaths / qmake options.
+ # We also don't do auto-searching if qt-target-path is passed
+ # explicitly. This is to help with the building of host tools
+ # while cross-compiling.
+ # Skip this process for the 'build_base_docs' command
+ if (not self.is_cross_compile
+ and not self.qt_target_path
+ and 'build_base_docs' not in sys.argv):
+ # Enforce usage of qmake in QtInfo if it was given explicitly.
+ if self.qmake:
+ self.has_qmake_option = True
+ _warn_deprecated_option('qmake', 'qtpaths')
+
+ # If no tool was specified and qtpaths was not found in PATH,
+ # ask to provide a path to qtpaths.
+ if not self.qtpaths and not self.qmake and not self.qt_target_path:
+ log.error("No value provided to --qtpaths option. Please provide one to find Qt.")
+ return False
+
+ # Validate that the given tool path exists.
+ if self.qtpaths and not self.qtpaths.exists():
+ log.error(f"The specified qtpaths path '{self.qtpaths}' does not exist.")
+ return False
+
+ if self.qmake and not self.qmake.exists():
+ log.error(f"The specified qmake path '{self.qmake}' does not exist.")
+ return False
+ else:
+ # Check for existence, but don't require if it's not set. A
+ # check later will be done to see if it's needed.
+ if self.qt_target_path and not self.qt_target_path.exists():
+ log.error(f"Provided --qt-target-path='{self.qt_target_path}' "
+ "path does not exist.")
+ return False
+
+ if not self.make_spec:
+ self.make_spec = _AVAILABLE_MKSPECS[0]
+ if self.make_spec not in _AVAILABLE_MKSPECS:
+ log.error(f'Invalid option --make-spec "{self.make_spec}". '
+ f'Available values are {_AVAILABLE_MKSPECS}')
+ return False
+
+ if OPTION["JOBS"] and sys.platform == 'win32' and self.no_jom:
+ log.error("Option --jobs can only be used with jom on Windows.")
+ return False
+
+ if sys.platform == 'win32' and OPTION["LIMITED_API"] == "yes" and self.debug:
+ log.error("It is not possible to make a debug build of PySide6 with limited API. "
+ "Please select a release build or disable limited API.")
+ return False
+
+ return True
diff --git a/build_scripts/platforms/__init__.py b/build_scripts/platforms/__init__.py
index 571d37492..853aaad7b 100644
--- a/build_scripts/platforms/__init__.py
+++ b/build_scripts/platforms/__init__.py
@@ -1,38 +1,2 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
+# Copyright (C) 2018 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
diff --git a/build_scripts/platforms/linux.py b/build_scripts/platforms/linux.py
index 712739e05..b4c66d94e 100644
--- a/build_scripts/platforms/linux.py
+++ b/build_scripts/platforms/linux.py
@@ -1,57 +1,26 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from ..utils import (copydir, copyfile, copy_icu_libs, find_files_using_glob,
- linux_set_rpaths, linux_run_read_elf, linux_get_rpaths,
- rpaths_has_origin)
+# Copyright (C) 2018 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+from pathlib import Path
+
+from ..log import log
from ..config import config
+from ..options import OPTION
+from ..utils import (copy_icu_libs, copydir, copyfile, find_files_using_glob,
+ linux_patch_executable)
+from .. import PYSIDE, PYSIDE_UNIX_BUNDLED_TOOLS
-def prepare_standalone_package_linux(self, vars):
- built_modules = vars['built_modules']
+def prepare_standalone_package_linux(pyside_build, _vars, cross_build=False, is_android=False):
+ built_modules = _vars['built_modules']
constrain_modules = None
copy_plugins = True
copy_qml = True
copy_translations = True
copy_qt_conf = True
- should_copy_icu_libs = True
+
+ log.info("Copying files...")
if config.is_internal_shiboken_generator_build():
constrain_modules = ["Core", "Network", "Xml", "XmlPatterns"]
@@ -59,88 +28,116 @@ def prepare_standalone_package_linux(self, vars):
copy_qml = False
copy_translations = False
copy_qt_conf = False
- should_copy_icu_libs = False
# <qt>/lib/* -> <setup>/{st_package_name}/Qt/lib
- destination_lib_dir = "{st_build_dir}/{st_package_name}/Qt/lib"
+ destination_dir = Path("{st_build_dir}/{st_package_name}".format(**_vars))
+ destination_qt_dir = destination_dir / "Qt"
+ destination_qt_lib_dir = destination_qt_dir / "lib"
+
+ # android libs does not have the Qt major version
+ if is_android:
+ lib_regex = 'libQt6*.so*'
+ else:
+ lib_regex = 'libQt6*.so.?'
- accepted_modules = ['libQt5*.so.?']
+ accepted_modules = [lib_regex]
if constrain_modules:
- accepted_modules = ["libQt5" + module + "*.so.?" for module in constrain_modules]
+ accepted_modules = [f"libQt6{module}*.so.?" if not is_android else f"libQt6{module}*.so*"
+ for module in constrain_modules]
accepted_modules.append("libicu*.so.??")
- copydir("{qt_lib_dir}", destination_lib_dir,
- filter=accepted_modules,
- recursive=False, vars=vars, force_copy_symlinks=True)
+ if is_android:
+ accepted_modules.append("*-android-dependencies.xml")
- if should_copy_icu_libs:
+ copydir("{qt_lib_dir}", destination_qt_lib_dir,
+ _filter=accepted_modules,
+ recursive=False, _vars=_vars, force_copy_symlinks=True)
+
+ if not cross_build and not is_android:
# 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*")
+ maybe_icu_libs = find_files_using_glob(destination_qt_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.
+ # custom written ldd (non-cross build only), 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 shiboken6 executable uses.
if not maybe_icu_libs:
- copy_icu_libs(self._patchelf_path, resolved_destination_lib_dir)
+ copy_icu_libs(pyside_build._patchelf_path, destination_qt_lib_dir)
+
+ # Set RPATH for Qt libs.
+ if not is_android:
+ pyside_build.update_rpath_for_linux_qt_libraries(destination_qt_lib_dir)
# Patching designer to use the Qt libraries provided in the wheel
- if config.is_internal_pyside_build():
- designer_path = "{st_build_dir}/{st_package_name}/designer".format(**vars)
- rpaths = linux_get_rpaths(designer_path)
- if not rpaths or not rpaths_has_origin(rpaths):
- rpaths.insert(0, '$ORIGIN/../lib')
- new_rpaths_string = ":".join(rpaths)
- linux_set_rpaths(self._patchelf_path, designer_path, new_rpaths_string)
-
- if self.is_webengine_built(built_modules):
- copydir("{qt_lib_execs_dir}",
- "{st_build_dir}/{st_package_name}/Qt/libexec",
- filter=None,
- recursive=False,
- vars=vars)
+ if config.is_internal_pyside_build() and not OPTION['NO_QT_TOOLS']:
+
+ for tool in PYSIDE_UNIX_BUNDLED_TOOLS:
+ tool_path = destination_dir / tool
+ linux_patch_executable(pyside_build._patchelf_path, tool_path)
- copydir("{qt_prefix_dir}/resources",
- "{st_build_dir}/{st_package_name}/Qt/resources",
- filter=None,
+ if pyside_build.is_webengine_built(built_modules):
+ copydir("{qt_data_dir}/resources",
+ destination_qt_dir / "resources",
+ _filter=None,
recursive=False,
- vars=vars)
+ _vars=_vars)
if copy_plugins:
+ is_pypy = "pypy" in pyside_build.build_classifiers
# <qt>/plugins/* -> <setup>/{st_package_name}/Qt/plugins
- copydir("{qt_plugins_dir}",
- "{st_build_dir}/{st_package_name}/Qt/plugins",
- filter=["*.so"],
+ plugins_target = destination_qt_dir / "plugins"
+ copydir("{qt_plugins_dir}", plugins_target,
+ _filter=["*.so"],
recursive=True,
- vars=vars)
+ _vars=_vars)
+ if not is_pypy and not is_android:
+ copydir("{install_dir}/plugins/designer",
+ plugins_target / "designer",
+ _filter=["*.so"],
+ recursive=False,
+ _vars=_vars)
+
+ copied_plugins = pyside_build.get_shared_libraries_in_path_recursively(
+ plugins_target)
+ if not is_android:
+ pyside_build.update_rpath_for_linux_plugins(copied_plugins)
if copy_qml:
# <qt>/qml/* -> <setup>/{st_package_name}/Qt/qml
+ qml_plugins_target = destination_qt_dir / "qml"
copydir("{qt_qml_dir}",
- "{st_build_dir}/{st_package_name}/Qt/qml",
- filter=None,
+ qml_plugins_target,
+ _filter=None,
force=False,
recursive=True,
- ignore=["*.so.debug"],
- vars=vars)
+ ignore=["*.debug"],
+ _vars=_vars)
+ copied_plugins = pyside_build.get_shared_libraries_in_path_recursively(
+ qml_plugins_target)
+ if not is_android:
+ pyside_build.update_rpath_for_linux_plugins(
+ copied_plugins,
+ qt_lib_dir=destination_qt_lib_dir,
+ is_qml_plugin=True)
if copy_translations:
# <qt>/translations/* ->
# <setup>/{st_package_name}/Qt/translations
copydir("{qt_translations_dir}",
- "{st_build_dir}/{st_package_name}/Qt/translations",
- filter=["*.qm", "*.pak"],
+ destination_qt_dir / "translations",
+ _filter=["*.qm", "*.pak"],
force=False,
- vars=vars)
+ _vars=_vars)
if copy_qt_conf:
# Copy the qt.conf file to libexec.
- copyfile(
- "{build_dir}/pyside2/{st_package_name}/qt.conf",
- "{st_build_dir}/{st_package_name}/Qt/libexec",
- vars=vars)
+ qt_libexec_path = destination_qt_dir / "libexec"
+ if not qt_libexec_path.is_dir():
+ qt_libexec_path.mkdir(parents=True)
+ copyfile(f"{{build_dir}}/{PYSIDE}/{{st_package_name}}/qt.conf",
+ qt_libexec_path, _vars=_vars)
diff --git a/build_scripts/platforms/macos.py b/build_scripts/platforms/macos.py
index 7932db337..dbe60d343 100644
--- a/build_scripts/platforms/macos.py
+++ b/build_scripts/platforms/macos.py
@@ -1,50 +1,28 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
+# Copyright (C) 2018 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
import fnmatch
-import os
-from ..utils import copydir, copyfile, macos_fix_rpaths_for_library, macos_add_rpath
+from pathlib import Path
+
+from ..log import log
from ..config import config
+from ..options import OPTION
+from ..utils import (copydir, copyfile, macos_add_rpath,
+ macos_fix_rpaths_for_library)
+from .. import PYSIDE, PYSIDE_UNIX_BUNDLED_TOOLS
+
+def _macos_patch_executable(name, _vars=None):
+ """ Patch an executable to run with the Qt libraries. """
+ upper_name = name.capitalize()
+ bundle = f"{{st_build_dir}}/{{st_package_name}}/{upper_name}.app".format(**_vars)
+ binary = f"{bundle}/Contents/MacOS/{upper_name}"
+ rpath = "@loader_path/../../../Qt/lib"
+ macos_add_rpath(rpath, binary)
-def prepare_standalone_package_macos(self, vars):
- built_modules = vars['built_modules']
+
+def prepare_standalone_package_macos(pyside_build, _vars):
+ built_modules = _vars['built_modules']
constrain_modules = None
copy_plugins = True
@@ -52,9 +30,14 @@ def prepare_standalone_package_macos(self, vars):
copy_translations = True
copy_qt_conf = True
+ destination_dir = Path("{st_build_dir}/{st_package_name}".format(**_vars))
+ destination_qt_dir = destination_dir / "Qt"
+ destination_qt_lib_dir = destination_qt_dir / "lib"
+ log.info("Copying files...")
+
if config.is_internal_shiboken_generator_build():
constrain_modules = ["Core", "Network", "Xml", "XmlPatterns"]
- constrain_frameworks = ['Qt' + name + '.framework' for name in constrain_modules]
+ constrain_frameworks = [f"Qt{name}.framework" for name in constrain_modules]
copy_plugins = False
copy_qml = False
copy_translations = False
@@ -71,36 +54,34 @@ def prepare_standalone_package_macos(self, vars):
no_copy_debug = True
def file_variant_filter(file_name, file_full_path):
- if self.qtinfo.build_type != 'debug_and_release':
+ if pyside_build.qtinfo.build_type != 'debug_and_release':
return True
if file_name.endswith('_debug.dylib') and no_copy_debug:
return False
return True
# Patching designer to use the Qt libraries provided in the wheel
- if config.is_internal_pyside_build():
- designer_bundle = "{st_build_dir}/{st_package_name}/Designer.app".format(**vars)
- designer_binary = "{}/Contents/MacOS/Designer".format(designer_bundle)
- rpath = "@loader_path/../../../Qt/lib"
- macos_add_rpath(rpath, designer_binary)
+ if config.is_internal_pyside_build() and not OPTION['NO_QT_TOOLS']:
+ for tool in PYSIDE_UNIX_BUNDLED_TOOLS:
+ _macos_patch_executable(tool, _vars)
# <qt>/lib/* -> <setup>/{st_package_name}/Qt/lib
- if self.qt_is_framework_build():
+ if pyside_build.qt_is_framework_build():
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)):
+ and not pyside_build.is_webengine_built(built_modules)):
return False
if constrain_modules and dir_name not in constrain_frameworks:
return False
if dir_name in ['Headers', 'fonts']:
return False
- if dir_full_path.endswith('Versions/Current'):
+ if str(dir_full_path).endswith('Versions/Current'):
return False
- if dir_full_path.endswith('Versions/5/Resources'):
+ if str(dir_full_path).endswith('Versions/5/Resources'):
return False
- if dir_full_path.endswith('Versions/5/Helpers'):
+ if str(dir_full_path).endswith('Versions/5/Helpers'):
return False
return general_dir_filter(dir_name, parent_full_path, dir_full_path)
@@ -109,16 +90,16 @@ def prepare_standalone_package_macos(self, vars):
no_copy_debug = True
def framework_variant_filter(file_name, file_full_path):
- if self.qtinfo.build_type != 'debug_and_release':
+ if pyside_build.qtinfo.build_type != 'debug_and_release':
return True
- dir_path = os.path.dirname(file_full_path)
+ dir_path = Path(file_full_path).parent
in_framework = dir_path.endswith("Versions/5")
if file_name.endswith('_debug') and in_framework and no_copy_debug:
return False
return True
- copydir("{qt_lib_dir}", "{st_build_dir}/{st_package_name}/Qt/lib",
- recursive=True, vars=vars,
+ copydir("{qt_lib_dir}", destination_qt_lib_dir,
+ recursive=True, _vars=_vars,
ignore=["*.la", "*.a", "*.cmake", "*.pc", "*.prl"],
dir_filter_function=framework_dir_filter,
file_filter_function=framework_variant_filter)
@@ -127,85 +108,81 @@ def prepare_standalone_package_macos(self, vars):
# 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 = "{st_build_dir}/{st_package_name}/Qt/lib".format(**vars)
- bundle = "QtWebEngineCore.framework/Helpers/"
- bundle += "QtWebEngineProcess.app"
+ if pyside_build.is_webengine_built(built_modules):
+ bundle = Path("QtWebEngineCore.framework/Helpers/") / "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)
+ webengine_process_path = bundle / binary
+ final_path = destination_qt_lib_dir / 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']
+ if not pyside_build.is_webengine_built(built_modules):
+ ignored_modules.extend(['libQt6WebEngine*.dylib'])
+ accepted_modules = ['libQt6*.6.dylib']
if constrain_modules:
- accepted_modules = ["libQt5" + module + "*.5.dylib" for module in constrain_modules]
+ accepted_modules = [f"libQt6{module}*.6.dylib" for module in constrain_modules]
- copydir("{qt_lib_dir}",
- "{st_build_dir}/{st_package_name}/Qt/lib",
- filter=accepted_modules,
+ copydir("{qt_lib_dir}", destination_qt_lib_dir,
+ _filter=accepted_modules,
ignore=ignored_modules,
file_filter_function=file_variant_filter,
- recursive=True, vars=vars, force_copy_symlinks=True)
+ recursive=True, _vars=_vars, force_copy_symlinks=True)
- if self.is_webengine_built(built_modules):
- copydir("{qt_lib_execs_dir}",
- "{st_build_dir}/{st_package_name}/Qt/libexec",
- filter=None,
+ if pyside_build.is_webengine_built(built_modules):
+ copydir("{qt_data_dir}/resources",
+ destination_qt_dir / "resources",
+ _filter=None,
recursive=False,
- vars=vars)
-
- copydir("{qt_prefix_dir}/resources",
- "{st_build_dir}/{st_package_name}/Qt/resources",
- filter=None,
- recursive=False,
- vars=vars)
+ _vars=_vars)
# Fix rpath for WebEngine process executable.
- qt_libexec_path = "{st_build_dir}/{st_package_name}/Qt/libexec".format(**vars)
+ qt_libexec_path = Path(destination_qt_dir) / "libexec"
binary = "QtWebEngineProcess"
- final_path = os.path.join(qt_libexec_path, binary)
+ final_path = qt_libexec_path / binary
rpath = "@loader_path/../lib"
macos_fix_rpaths_for_library(final_path, rpath)
if copy_qt_conf:
# Copy the qt.conf file to libexec.
+ if not qt_libexec_path.is_dir():
+ qt_libexec_path.mkdir(parents=True)
copyfile(
- "{build_dir}/pyside2/{st_package_name}/qt.conf",
- "{st_build_dir}/{st_package_name}/Qt/libexec",
- vars=vars)
+ f"{{build_dir}}/{PYSIDE}/{{st_package_name}}/qt.conf",
+ qt_libexec_path, _vars=_vars)
if copy_plugins:
+ is_pypy = "pypy" in pyside_build.build_classifiers
# <qt>/plugins/* -> <setup>/{st_package_name}/Qt/plugins
- copydir("{qt_plugins_dir}",
- "{st_build_dir}/{st_package_name}/Qt/plugins",
- filter=["*.dylib"],
+ plugins_target = destination_qt_dir / "plugins"
+ filters = ["*.dylib"]
+ copydir("{qt_plugins_dir}", plugins_target,
+ _filter=filters,
recursive=True,
dir_filter_function=general_dir_filter,
file_filter_function=file_variant_filter,
- vars=vars)
+ _vars=_vars)
+ if not is_pypy:
+ copydir("{install_dir}/plugins/designer",
+ plugins_target / "designer",
+ _filter=filters,
+ recursive=False,
+ _vars=_vars)
if copy_qml:
# <qt>/qml/* -> <setup>/{st_package_name}/Qt/qml
- copydir("{qt_qml_dir}",
- "{st_build_dir}/{st_package_name}/Qt/qml",
- filter=None,
+ copydir("{qt_qml_dir}", destination_qt_dir / "qml",
+ _filter=None,
recursive=True,
force=False,
dir_filter_function=general_dir_filter,
file_filter_function=file_variant_filter,
- vars=vars)
+ _vars=_vars)
if copy_translations:
# <qt>/translations/* ->
# <setup>/{st_package_name}/Qt/translations
- copydir("{qt_translations_dir}",
- "{st_build_dir}/{st_package_name}/Qt/translations",
- filter=["*.qm", "*.pak"],
+ copydir("{qt_translations_dir}", destination_qt_dir / "translations",
+ _filter=["*.qm", "*.pak"],
force=False,
- vars=vars)
+ _vars=_vars)
diff --git a/build_scripts/platforms/unix.py b/build_scripts/platforms/unix.py
index b842510ff..3333f5f96 100644
--- a/build_scripts/platforms/unix.py
+++ b/build_scripts/platforms/unix.py
@@ -1,227 +1,253 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-import os
+# Copyright (C) 2018 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
import sys
-import fnmatch
-from .linux import prepare_standalone_package_linux
-from .macos import prepare_standalone_package_macos
+from pathlib import Path
+from ..log import log
from ..config import config
from ..options import OPTION
-from ..utils import copydir, copyfile, makefile
-from ..utils import regenerate_qt_resources
+from ..utils import copydir, copyfile, copy_qt_metatypes, makefile
+from .. import PYSIDE, SHIBOKEN
+from .linux import prepare_standalone_package_linux
+from .macos import prepare_standalone_package_macos
+from .. import PYSIDE_UNIX_BIN_TOOLS, PYSIDE_UNIX_LIBEXEC_TOOLS, PYSIDE_UNIX_BUNDLED_TOOLS
+
+
+def _macos_copy_gui_executable(name, _vars=None):
+ """macOS helper: Copy a GUI executable from the .app folder and return the
+ files"""
+ app_name = f"{name.capitalize()}.app"
+ return copydir(f"{{install_dir}}/bin/{app_name}",
+ f"{{st_build_dir}}/{{st_package_name}}/{app_name}",
+ _filter=None, recursive=True,
+ force=False, _vars=_vars)
-def prepare_packages_posix(self, vars):
+def _unix_copy_gui_executable(name, _vars=None):
+ """UNIX helper: Copy a GUI executable and return the files"""
+ return copydir("{install_dir}/bin/",
+ "{st_build_dir}/{st_package_name}/",
+ _filter=[name],
+ force=False, _vars=_vars)
+
+
+def _copy_gui_executable(name, _vars=None):
+ """Copy a GUI executable and return the files"""
+ if sys.platform == 'darwin':
+ return _macos_copy_gui_executable(name, _vars)
+ return _unix_copy_gui_executable(name, _vars)
+
+
+def prepare_packages_posix(pyside_build, _vars, cross_build=False):
+ is_android = False
+ if str(OPTION['PLAT_NAME']).startswith('android'):
+ is_android = True
+
executables = []
+ libexec_executables = []
+ log.info("Copying files...")
+
+ destination_dir = Path("{st_build_dir}/{st_package_name}".format(**_vars))
+ destination_qt_dir = destination_dir / "Qt"
# <install>/lib/site-packages/{st_package_name}/* ->
# <setup>/{st_package_name}
# This copies the module .so/.dylib files and various .py files
# (__init__, config, git version, etc.)
copydir(
- "{site_packages_dir}/{st_package_name}",
- "{st_build_dir}/{st_package_name}",
- vars=vars)
+ "{site_packages_dir}/{st_package_name}", destination_dir,
+ _vars=_vars)
- generated_config = self.get_built_pyside_config(vars)
+ generated_config = pyside_build.get_built_pyside_config(_vars)
def adjusted_lib_name(name, version):
postfix = ''
- if sys.platform.startswith('linux'):
- postfix = '.so.' + version
+ if config.is_cross_compile() and is_android:
+ postfix = ".so"
+ elif sys.platform.startswith('linux'):
+ postfix = f".so.{version}"
elif sys.platform == 'darwin':
- postfix = '.' + version + '.dylib'
+ postfix = f".{version}.dylib"
return name + postfix
if config.is_internal_shiboken_module_build():
- # <build>/shiboken2/doc/html/* ->
- # <setup>/{st_package_name}/docs/shiboken2
+ # <build>/shiboken6/doc/html/* ->
+ # <setup>/{st_package_name}/docs/shiboken6
copydir(
- "{build_dir}/shiboken2/doc/html",
- "{st_build_dir}/{st_package_name}/docs/shiboken2",
- force=False, vars=vars)
+ f"{{build_dir}}/{SHIBOKEN}/doc/html",
+ f"{{st_build_dir}}/{{st_package_name}}/docs/{SHIBOKEN}",
+ force=False, _vars=_vars)
# <install>/lib/lib* -> {st_package_name}/
copydir(
- "{install_dir}/lib/",
- "{st_build_dir}/{st_package_name}",
- filter=[
+ "{install_dir}/lib/", destination_dir,
+ _filter=[
adjusted_lib_name("libshiboken*",
generated_config['shiboken_library_soversion']),
],
- recursive=False, vars=vars, force_copy_symlinks=True)
+ recursive=False, _vars=_vars, force_copy_symlinks=True)
if config.is_internal_shiboken_generator_build():
# <install>/bin/* -> {st_package_name}/
- executables.extend(copydir(
- "{install_dir}/bin/",
- "{st_build_dir}/{st_package_name}",
- filter=[
- "shiboken2",
- ],
- recursive=False, vars=vars))
+ copydir(
+ "{install_dir}/bin/", destination_dir,
+ _filter=[SHIBOKEN],
+ recursive=False, _vars=_vars)
# Used to create scripts directory.
makefile(
"{st_build_dir}/{st_package_name}/scripts/shiboken_tool.py",
- vars=vars)
+ _vars=_vars)
# For setting up setuptools entry points.
copyfile(
"{install_dir}/bin/shiboken_tool.py",
"{st_build_dir}/{st_package_name}/scripts/shiboken_tool.py",
- force=False, vars=vars)
+ force=False, _vars=_vars)
if config.is_internal_shiboken_generator_build() or config.is_internal_pyside_build():
# <install>/include/* -> <setup>/{st_package_name}/include
copydir(
"{install_dir}/include/{cmake_package_name}",
"{st_build_dir}/{st_package_name}/include",
- vars=vars)
+ _vars=_vars)
if config.is_internal_pyside_build():
- makefile(
- "{st_build_dir}/{st_package_name}/scripts/__init__.py",
- vars=vars)
-
- # For setting up setuptools entry points
- copyfile(
- "{install_dir}/bin/pyside_tool.py",
- "{st_build_dir}/{st_package_name}/scripts/pyside_tool.py",
- force=False, vars=vars)
-
- # <install>/bin/* -> {st_package_name}/
- executables.extend(copydir(
- "{install_dir}/bin/",
- "{st_build_dir}/{st_package_name}",
- filter=[
- "pyside2-lupdate",
- "uic",
- "rcc",
- ],
- recursive=False, vars=vars))
-
- # Copying designer
- if sys.platform == "darwin":
+ if not is_android:
+ makefile(
+ "{st_build_dir}/{st_package_name}/scripts/__init__.py",
+ _vars=_vars)
+
+ scripts = ["pyside_tool.py", "metaobjectdump.py", "project.py", "qml.py",
+ "qtpy2cpp.py", "deploy.py"]
+
+ script_dirs = ["qtpy2cpp_lib", "deploy_lib", "project"]
+
+ if sys.platform.startswith("linux"):
+ scripts.append("android_deploy.py")
+ scripts.append("requirements-android.txt")
+ script_dirs.extend(["deploy_lib/android",
+ "deploy_lib/android/recipes/PySide6",
+ "deploy_lib/android/recipes/shiboken6",])
+
+ # For setting up setuptools entry points
+ for script in scripts:
+ src = f"{{install_dir}}/bin/{script}"
+ target = f"{{st_build_dir}}/{{st_package_name}}/scripts/{script}"
+ copyfile(src, target, force=False, _vars=_vars)
+
+ for script_dir in script_dirs:
+ src = f"{{install_dir}}/bin/{script_dir}"
+ target = f"{{st_build_dir}}/{{st_package_name}}/scripts/{script_dir}"
+ # Exclude subdirectory tests
+ copydir(src, target, _filter=["*.py", "*.spec", "*.jpg", "*.icns", "*.ico"],
+ recursive=False, _vars=_vars)
+
+ # <install>/bin/* -> {st_package_name}/
executables.extend(copydir(
- "{install_dir}/bin/Designer.app",
- "{st_build_dir}/{st_package_name}/Designer.app",
- filter=None, recursive=True,
- force=False, vars=vars))
- else:
- copyfile(
- "{install_dir}/bin/designer",
- "{st_build_dir}/{st_package_name}/designer",
- force=False, vars=vars)
+ "{install_dir}/bin/", destination_dir,
+ _filter=[f"{PYSIDE}-lupdate"],
+ recursive=False, _vars=_vars))
+
+ lib_exec_filters = []
+ if not OPTION['NO_QT_TOOLS']:
+ lib_exec_filters.extend(PYSIDE_UNIX_LIBEXEC_TOOLS)
+ executables.extend(copydir(
+ "{install_dir}/bin/", destination_dir,
+ _filter=PYSIDE_UNIX_BIN_TOOLS,
+ recursive=False, _vars=_vars))
+
+ # Copying assistant/designer/linguist
+ for tool in PYSIDE_UNIX_BUNDLED_TOOLS:
+ executables.extend(_copy_gui_executable(tool, _vars=_vars))
+
+ copy_qt_metatypes(destination_qt_dir, _vars)
+
+ # Copy libexec
+ built_modules = pyside_build.get_built_pyside_config(_vars)['built_modules']
+ if pyside_build.is_webengine_built(built_modules):
+ lib_exec_filters.append('QtWebEngineProcess')
+ if lib_exec_filters:
+ libexec_executables.extend(copydir("{qt_lib_execs_dir}",
+ destination_qt_dir / "libexec",
+ _filter=lib_exec_filters,
+ recursive=False,
+ _vars=_vars))
# <install>/lib/lib* -> {st_package_name}/
copydir(
- "{install_dir}/lib/",
- "{st_build_dir}/{st_package_name}",
- filter=[
+ "{install_dir}/lib", destination_dir,
+ _filter=[
adjusted_lib_name("libpyside*",
generated_config['pyside_library_soversion']),
],
- recursive=False, vars=vars, force_copy_symlinks=True)
-
- # <install>/share/{st_package_name}/typesystems/* ->
- # <setup>/{st_package_name}/typesystems
- copydir(
- "{install_dir}/share/{st_package_name}/typesystems",
- "{st_build_dir}/{st_package_name}/typesystems",
- vars=vars)
-
- # <install>/share/{st_package_name}/glue/* ->
- # <setup>/{st_package_name}/glue
+ recursive=False, _vars=_vars, force_copy_symlinks=True)
+
+ copydir("{qt_module_json_files_dir}",
+ destination_qt_dir / "modules",
+ _filter=["*.json"], _vars=_vars)
+
+ if not config.is_cross_compile():
+ # <install>/share/{st_package_name}/typesystems/* ->
+ # <setup>/{st_package_name}/typesystems
+ copydir(
+ "{install_dir}/share/{st_package_name}/typesystems",
+ "{st_build_dir}/{st_package_name}/typesystems",
+ _vars=_vars)
+
+ # <install>/share/{st_package_name}/glue/* ->
+ # <setup>/{st_package_name}/glue
+ copydir(
+ "{install_dir}/share/{st_package_name}/glue",
+ "{st_build_dir}/{st_package_name}/glue",
+ _vars=_vars)
+
+ if not is_android:
+ # <source>/pyside6/{st_package_name}/support/* ->
+ # <setup>/{st_package_name}/support/*
+ copydir(
+ f"{{build_dir}}/{PYSIDE}/{{st_package_name}}/support",
+ "{st_build_dir}/{st_package_name}/support",
+ _vars=_vars)
+
+ # <source>/pyside6/{st_package_name}/QtAsyncio/* ->
+ # <setup>/{st_package_name}/QtAsyncio/*
copydir(
- "{install_dir}/share/{st_package_name}/glue",
- "{st_build_dir}/{st_package_name}/glue",
- vars=vars)
+ "{site_packages_dir}/{st_package_name}/QtAsyncio",
+ "{st_build_dir}/{st_package_name}/QtAsyncio",
+ _vars=_vars)
- # <source>/pyside2/{st_package_name}/support/* ->
- # <setup>/{st_package_name}/support/*
- copydir(
- "{build_dir}/pyside2/{st_package_name}/support",
- "{st_build_dir}/{st_package_name}/support",
- vars=vars)
-
- # <source>/pyside2/{st_package_name}/*.pyi ->
+ # <source>/pyside6/{st_package_name}/*.pyi ->
# <setup>/{st_package_name}/*.pyi
copydir(
- "{build_dir}/pyside2/{st_package_name}",
- "{st_build_dir}/{st_package_name}",
- filter=["*.pyi", "py.typed"],
- vars=vars)
-
- if not OPTION["NOEXAMPLES"]:
- def pycache_dir_filter(dir_name, parent_full_path, dir_full_path):
- if fnmatch.fnmatch(dir_name, "__pycache__"):
- return False
- return True
- # examples/* -> <setup>/{st_package_name}/examples
- copydir(os.path.join(self.script_dir, "examples"),
- "{st_build_dir}/{st_package_name}/examples",
- force=False, vars=vars, dir_filter_function=pycache_dir_filter)
- # Re-generate examples Qt resource files for Python 3
- # compatibility
- if sys.version_info[0] == 3:
- examples_path = "{st_build_dir}/{st_package_name}/examples".format(**vars)
- pyside_rcc_path = "{install_dir}/bin/rcc".format(**vars)
- pyside_rcc_options = ['-g', 'python']
- regenerate_qt_resources(examples_path, pyside_rcc_path, pyside_rcc_options)
+ f"{{build_dir}}/{PYSIDE}/{{st_package_name}}", destination_dir,
+ _filter=["*.pyi", "py.typed"],
+ _vars=_vars)
+
+ # copy the jar files
+ if is_android:
+ copydir(
+ "{install_dir}/lib/jar",
+ "{st_build_dir}/{st_package_name}/jar",
+ _vars=_vars)
# Copy Qt libs to package
if OPTION["STANDALONE"]:
if config.is_internal_pyside_build() or config.is_internal_shiboken_generator_build():
- vars['built_modules'] = generated_config['built_modules']
+ _vars['built_modules'] = generated_config['built_modules']
if sys.platform == 'darwin':
- prepare_standalone_package_macos(self, vars)
+ prepare_standalone_package_macos(pyside_build, _vars)
else:
- prepare_standalone_package_linux(self, vars)
+ prepare_standalone_package_linux(pyside_build, _vars, cross_build,
+ is_android=is_android)
if config.is_internal_shiboken_generator_build():
# Copy over clang before rpath patching.
- self.prepare_standalone_clang(is_win=False)
+ pyside_build.prepare_standalone_clang(is_win=False)
# Update rpath to $ORIGIN
- if sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
- rpath_path = "{st_build_dir}/{st_package_name}".format(**vars)
- self.update_rpath(rpath_path, executables)
+ if (sys.platform.startswith('linux') or sys.platform.startswith('darwin')) and not is_android:
+ pyside_build.update_rpath(executables)
+ if libexec_executables:
+ pyside_build.update_rpath(libexec_executables, libexec=True)
diff --git a/build_scripts/platforms/windows_desktop.py b/build_scripts/platforms/windows_desktop.py
index 750a064b4..9c29953be 100644
--- a/build_scripts/platforms/windows_desktop.py
+++ b/build_scripts/platforms/windows_desktop.py
@@ -1,281 +1,273 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
+# Copyright (C) 2018 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
import functools
import os
-import sys
-import fnmatch
+import tempfile
+from pathlib import Path
+
+from ..log import log
from ..config import config
from ..options import OPTION
-from ..utils import copydir, copyfile, makefile
-from ..utils import regenerate_qt_resources, filter_match
-from ..utils import download_and_extract_7z
+from ..utils import (copydir, copyfile, copy_qt_metatypes,
+ download_and_extract_7z, filter_match, makefile)
+from .. import PYSIDE, SHIBOKEN, PYSIDE_WINDOWS_BIN_TOOLS
-def prepare_packages_win32(self, vars):
+def prepare_packages_win32(pyside_build, _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:
+ if (pyside_build.debug or pyside_build.build_type == 'RelWithDebInfo') and copy_pdbs:
pdbs = ['*.pdb']
+ destination_dir = Path("{st_build_dir}/{st_package_name}".format(**_vars))
+ destination_qt_dir = destination_dir
+ log.info("Copying files...")
+
# <install>/lib/site-packages/{st_package_name}/* ->
# <setup>/{st_package_name}
# This copies the module .pyd files and various .py files
# (__init__, config, git version, etc.)
copydir(
- "{site_packages_dir}/{st_package_name}",
- "{st_build_dir}/{st_package_name}",
- vars=vars)
+ "{site_packages_dir}/{st_package_name}", destination_dir,
+ _vars=_vars)
if config.is_internal_shiboken_module_build():
- # <build>/shiboken2/doc/html/* ->
- # <setup>/{st_package_name}/docs/shiboken2
+ # <build>/shiboken6/doc/html/* ->
+ # <setup>/{st_package_name}/docs/shiboken6
copydir(
- "{build_dir}/shiboken2/doc/html",
- "{st_build_dir}/{st_package_name}/docs/shiboken2",
- force=False, vars=vars)
+ f"{{build_dir}}/{SHIBOKEN}/doc/html",
+ f"{{st_build_dir}}/{{st_package_name}}/docs/{SHIBOKEN}",
+ force=False, _vars=_vars)
# <install>/bin/*.dll -> {st_package_name}/
copydir(
- "{install_dir}/bin/",
- "{st_build_dir}/{st_package_name}",
- filter=["shiboken*.dll"],
- recursive=False, vars=vars)
+ "{install_dir}/bin/", destination_qt_dir,
+ _filter=["shiboken*.dll"],
+ recursive=False, _vars=_vars)
# <install>/lib/*.lib -> {st_package_name}/
copydir(
- "{install_dir}/lib/",
- "{st_build_dir}/{st_package_name}",
- filter=["shiboken*.lib"],
- recursive=False, vars=vars)
+ "{install_dir}/lib/", destination_qt_dir,
+ _filter=["shiboken*.lib"],
+ recursive=False, _vars=_vars)
# @TODO: Fix this .pdb file not to overwrite release
# {shibokengenerator}.pdb file.
# Task-number: PYSIDE-615
copydir(
- "{build_dir}/shiboken2/shibokenmodule",
- "{st_build_dir}/{st_package_name}",
- filter=pdbs,
- recursive=False, vars=vars)
+ f"{{build_dir}}/{SHIBOKEN}/shibokenmodule", destination_dir,
+ _filter=pdbs,
+ recursive=False, _vars=_vars)
# pdb files for libshiboken and libpyside
copydir(
- "{build_dir}/shiboken2/libshiboken",
- "{st_build_dir}/{st_package_name}",
- filter=pdbs,
- recursive=False, vars=vars)
+ f"{{build_dir}}/{SHIBOKEN}/libshiboken", destination_dir,
+ _filter=pdbs,
+ recursive=False, _vars=_vars)
if config.is_internal_shiboken_generator_build():
# <install>/bin/*.dll -> {st_package_name}/
copydir(
- "{install_dir}/bin/",
- "{st_build_dir}/{st_package_name}",
- filter=["shiboken*.exe"],
- recursive=False, vars=vars)
+ "{install_dir}/bin/", destination_dir,
+ _filter=["shiboken*.exe"],
+ recursive=False, _vars=_vars)
# Used to create scripts directory.
- makefile(
- "{st_build_dir}/{st_package_name}/scripts/shiboken_tool.py",
- vars=vars)
+ makefile(f"{destination_dir}/scripts/shiboken_tool.py", _vars=_vars)
# For setting up setuptools entry points.
copyfile(
"{install_dir}/bin/shiboken_tool.py",
- "{st_build_dir}/{st_package_name}/scripts/shiboken_tool.py",
- force=False, vars=vars)
+ f"{destination_dir}/scripts/shiboken_tool.py",
+ force=False, _vars=_vars)
# @TODO: Fix this .pdb file not to overwrite release
# {shibokenmodule}.pdb file.
# Task-number: PYSIDE-615
copydir(
- "{build_dir}/shiboken2/generator",
- "{st_build_dir}/{st_package_name}",
- filter=pdbs,
- recursive=False, vars=vars)
+ f"{{build_dir}}/{SHIBOKEN}/generator", destination_dir,
+ _filter=pdbs,
+ recursive=False, _vars=_vars)
if config.is_internal_shiboken_generator_build() or config.is_internal_pyside_build():
# <install>/include/* -> <setup>/{st_package_name}/include
copydir(
"{install_dir}/include/{cmake_package_name}",
- "{st_build_dir}/{st_package_name}/include",
- vars=vars)
+ destination_dir / "include",
+ _vars=_vars)
if config.is_internal_pyside_build():
- # <build>/pyside2/{st_package_name}/*.pdb ->
+ # <build>/pyside6/{st_package_name}/*.pdb ->
# <setup>/{st_package_name}
copydir(
- "{build_dir}/pyside2/{st_package_name}",
- "{st_build_dir}/{st_package_name}",
- filter=pdbs,
- recursive=False, vars=vars)
+ f"{{build_dir}}/{PYSIDE}/{{st_package_name}}", destination_dir,
+ _filter=pdbs,
+ recursive=False, _vars=_vars)
- makefile(
- "{st_build_dir}/{st_package_name}/scripts/__init__.py",
- vars=vars)
+ makefile(f"{destination_dir}/scripts/__init__.py", _vars=_vars)
# For setting up setuptools entry points
- copyfile(
- "{install_dir}/bin/pyside_tool.py",
- "{st_build_dir}/{st_package_name}/scripts/pyside_tool.py",
- force=False, vars=vars)
+ for script in ("pyside_tool.py", "metaobjectdump.py", "project.py", "qml.py",
+ "qtpy2cpp.py", "deploy.py"):
+ src = f"{{install_dir}}/bin/{script}"
+ target = f"{{st_build_dir}}/{{st_package_name}}/scripts/{script}"
+ copyfile(src, target, force=False, _vars=_vars)
+
+ for script_dir in ("qtpy2cpp_lib", "deploy_lib", "project"):
+ src = f"{{install_dir}}/bin/{script_dir}"
+ target = f"{{st_build_dir}}/{{st_package_name}}/scripts/{script_dir}"
+ # Exclude subdirectory tests
+ copydir(src, target, _filter=["*.py", "*.spec", "*.jpg", "*.icns", "*.ico"],
+ recursive=False, _vars=_vars)
# <install>/bin/*.exe,*.dll -> {st_package_name}/
- copydir(
- "{install_dir}/bin/",
- "{st_build_dir}/{st_package_name}",
- filter=["pyside*.exe", "pyside*.dll", "uic.exe", "rcc.exe", "designer.exe"],
- recursive=False, vars=vars)
+ filters = ["pyside*.exe", "pyside*.dll"]
+ if not OPTION['NO_QT_TOOLS']:
+ filters.extend([f"{tool}.exe" for tool in PYSIDE_WINDOWS_BIN_TOOLS])
+ copydir("{install_dir}/bin/", destination_qt_dir,
+ _filter=filters,
+ recursive=False, _vars=_vars)
+
+ copy_qt_metatypes(destination_qt_dir, _vars)
# <install>/lib/*.lib -> {st_package_name}/
copydir(
- "{install_dir}/lib/",
- "{st_build_dir}/{st_package_name}",
- filter=["pyside*.lib"],
- recursive=False, vars=vars)
+ "{install_dir}/lib/", destination_dir,
+ _filter=["pyside*.lib"],
+ recursive=False, _vars=_vars)
+
+ copydir("{qt_module_json_files_dir}",
+ destination_qt_dir / "modules",
+ _filter=["*.json"], _vars=_vars)
# <install>/share/{st_package_name}/typesystems/* ->
# <setup>/{st_package_name}/typesystems
copydir(
"{install_dir}/share/{st_package_name}/typesystems",
- "{st_build_dir}/{st_package_name}/typesystems",
- vars=vars)
+ destination_dir / "typesystems",
+ _vars=_vars)
# <install>/share/{st_package_name}/glue/* ->
# <setup>/{st_package_name}/glue
copydir(
"{install_dir}/share/{st_package_name}/glue",
- "{st_build_dir}/{st_package_name}/glue",
- vars=vars)
+ destination_dir / "glue",
+ _vars=_vars)
- # <source>/pyside2/{st_package_name}/support/* ->
+ # <source>/pyside6/{st_package_name}/support/* ->
# <setup>/{st_package_name}/support/*
copydir(
- "{build_dir}/pyside2/{st_package_name}/support",
- "{st_build_dir}/{st_package_name}/support",
- vars=vars)
+ f"{{build_dir}}/{PYSIDE}/{{st_package_name}}/support",
+ destination_dir / "support",
+ _vars=_vars)
+
+ # <source>/pyside6/{st_package_name}/QtAsyncio/* ->
+ # <setup>/{st_package_name}/QtAsyncio/*
+ copydir(
+ "{site_packages_dir}/{st_package_name}/QtAsyncio",
+ "{st_build_dir}/{st_package_name}/QtAsyncio",
+ _vars=_vars)
- # <source>/pyside2/{st_package_name}/*.pyi ->
+ # <source>/pyside6/{st_package_name}/*.pyi ->
# <setup>/{st_package_name}/*.pyi
copydir(
- "{build_dir}/pyside2/{st_package_name}",
- "{st_build_dir}/{st_package_name}",
- filter=["*.pyi", "py.typed"],
- vars=vars)
+ f"{{build_dir}}/{PYSIDE}/{{st_package_name}}", destination_dir,
+ _filter=["*.pyi", "py.typed"],
+ _vars=_vars)
copydir(
- "{build_dir}/pyside2/libpyside",
- "{st_build_dir}/{st_package_name}",
- filter=pdbs,
- recursive=False, vars=vars)
-
- if not OPTION["NOEXAMPLES"]:
- def pycache_dir_filter(dir_name, parent_full_path, dir_full_path):
- if fnmatch.fnmatch(dir_name, "__pycache__"):
- return False
- return True
- # examples/* -> <setup>/{st_package_name}/examples
- copydir(os.path.join(self.script_dir, "examples"),
- "{st_build_dir}/{st_package_name}/examples",
- force=False, vars=vars, dir_filter_function=pycache_dir_filter)
- # Re-generate examples Qt resource files for Python 3
- # compatibility
- if sys.version_info[0] == 3:
- examples_path = "{st_build_dir}/{st_package_name}/examples".format(
- **vars)
- pyside_rcc_path = "{install_dir}/bin/rcc.exe".format(
- **vars)
- pyside_rcc_options = ['-g', 'python']
- regenerate_qt_resources(examples_path, pyside_rcc_path, pyside_rcc_options)
-
- if vars['ssl_libs_dir']:
+ f"{{build_dir}}/{PYSIDE}/libpyside", destination_dir,
+ _filter=pdbs,
+ recursive=False, _vars=_vars)
+
+ if _vars['ssl_libs_dir']:
# <ssl_libs>/* -> <setup>/{st_package_name}/openssl
- copydir("{ssl_libs_dir}", "{st_build_dir}/{st_package_name}/openssl",
- filter=[
+ copydir("{ssl_libs_dir}", destination_dir / "openssl",
+ _filter=[
"libeay32.dll",
"ssleay32.dll"],
- force=False, vars=vars)
+ force=False, _vars=_vars)
if config.is_internal_shiboken_module_build():
# The C++ std library dlls need to be packaged with the
# shiboken module, because libshiboken uses C++ code.
- copy_msvc_redist_files(vars, "{build_dir}/msvc_redist".format(**vars))
+ copy_msvc_redist_files(destination_dir)
if config.is_internal_pyside_build() or config.is_internal_shiboken_generator_build():
- copy_qt_artifacts(self, copy_pdbs, vars)
-
-
-def copy_msvc_redist_files(vars, redist_target_path):
- # MSVC redistributable file list.
- msvc_redist = [
- "concrt140.dll",
- "msvcp140.dll",
- "ucrtbase.dll",
- "vcamp140.dll",
- "vccorlib140.dll",
- "vcomp140.dll",
- "vcruntime140.dll"
- ]
+ copy_qt_artifacts(pyside_build, destination_qt_dir, copy_pdbs, _vars)
+ copy_msvc_redist_files(destination_dir)
+
+
+# MSVC redistributable file list.
+msvc_redist = [
+ "concrt140.dll",
+ "msvcp140.dll",
+ "vcamp140.dll",
+ "vccorlib140.dll",
+ "vcomp140.dll",
+ "vcruntime140.dll",
+ "vcruntime140_1.dll",
+ "msvcp140_1.dll",
+ "msvcp140_2.dll",
+ "msvcp140_codecvt_ids.dll"
+]
+
+
+def copy_msvc_redist_files(destination_dir):
+ in_coin = os.environ.get('COIN_LAUNCH_PARAMETERS', None)
+ if in_coin is None:
+ log.info("Qt dependency DLLs (MSVC redist) will not be copied.")
+ return
# Make a directory where the files should be extracted.
- if not os.path.exists(redist_target_path):
- os.makedirs(redist_target_path)
-
+ if not destination_dir.exists():
+ destination_dir.mkdir(parents=True)
+
+ # Copy Qt dependency DLLs (MSVC) from PATH when building on Qt CI.
+ paths = os.environ["PATH"].split(os.pathsep)
+ for path in paths:
+ try:
+ for f in Path(path).glob("*140*.dll"):
+ if f.name in msvc_redist:
+ copyfile(f, Path(destination_dir) / f.name)
+ msvc_redist.remove(f.name)
+ if not msvc_redist:
+ break
+ except WindowsError:
+ continue
+
+ if msvc_redist:
+ msg = "The following Qt dependency DLLs (MSVC redist) were not found: {msvc_redist}"
+ raise FileNotFoundError(msg)
+
+
+def copy_qt_dependency_dlls(_vars, destination_qt_dir, artifacts):
# Extract Qt dependency dlls when building on Qt CI.
in_coin = os.environ.get('COIN_LAUNCH_PARAMETERS', None)
- if in_coin is not None:
- redist_url = "http://download.qt.io/development_releases/prebuilt/vcredist/"
- zip_file = "pyside_qt_deps_64.7z"
- if "{target_arch}".format(**vars) == "32":
- zip_file = "pyside_qt_deps_32.7z"
- download_and_extract_7z(redist_url + zip_file, redist_target_path)
- else:
- print("Qt dependency DLLs (MSVC redist) will not be downloaded and extracted.")
-
- copydir(redist_target_path,
- "{st_build_dir}/{st_package_name}",
- filter=msvc_redist, recursive=False, vars=vars)
-
-
-def copy_qt_artifacts(self, copy_pdbs, vars):
- built_modules = self.get_built_pyside_config(vars)['built_modules']
+ if in_coin is None:
+ log.info("Qt dependency DLLs will not be downloaded and extracted.")
+ return
+
+ with tempfile.TemporaryDirectory() as temp_path:
+ redist_url = "https://download.qt.io/development_releases/prebuilt/vcredist/"
+ zip_file = "pyside_qt_deps_64_2019.7z"
+ if "{target_arch}".format(**_vars) == "32":
+ zip_file = "pyside_qt_deps_32_2019.7z"
+ try:
+ download_and_extract_7z(redist_url + zip_file, temp_path)
+ except Exception as e:
+ log.warning(f"Download failed: {type(e).__name__}: {e}")
+ log.warning("download.qt.io is down, try with mirror")
+ redist_url = "https://master.qt.io/development_releases/prebuilt/vcredist/"
+ download_and_extract_7z(redist_url + zip_file, temp_path)
+ copydir(temp_path, destination_qt_dir, _filter=artifacts, recursive=False, _vars=_vars)
+
+
+def copy_qt_artifacts(pyside_build, destination_qt_dir, copy_pdbs, _vars):
+ built_modules = pyside_build.get_built_pyside_config(_vars)['built_modules']
constrain_modules = None
copy_plugins = True
@@ -283,7 +275,6 @@ def copy_qt_artifacts(self, copy_pdbs, vars):
copy_translations = True
copy_qt_conf = True
copy_qt_permanent_artifacts = True
- copy_msvc_redist = False
copy_clang = False
if config.is_internal_shiboken_generator_build():
@@ -293,13 +284,16 @@ def copy_qt_artifacts(self, copy_pdbs, vars):
copy_translations = False
copy_qt_conf = False
copy_qt_permanent_artifacts = False
- copy_msvc_redist = True
copy_clang = True
# <qt>/bin/*.dll and Qt *.exe -> <setup>/{st_package_name}
qt_artifacts_permanent = [
+ "avcodec-60.dll",
+ "avformat-60.dll",
+ "avutil-58.dll",
+ "swresample-4.dll",
+ "swscale-7.dll",
"opengl*.dll",
- "d3d*.dll",
"designer.exe",
"linguist.exe",
"lrelease.exe",
@@ -313,41 +307,28 @@ def copy_qt_artifacts(self, copy_pdbs, vars):
"libEGL{}.dll",
"libGLESv2{}.dll"
]
- if self.qtinfo.build_type != 'debug_and_release':
+ if pyside_build.qtinfo.build_type != 'debug_and_release':
egl_suffix = '*'
- elif self.debug:
+ elif pyside_build.debug:
egl_suffix = 'd'
else:
egl_suffix = ''
qt_artifacts_egl = [a.format(egl_suffix) for a in qt_artifacts_egl]
- artifacts = []
if copy_qt_permanent_artifacts:
- artifacts += qt_artifacts_permanent
- artifacts += qt_artifacts_egl
-
- if copy_msvc_redist:
- # The target path has to be qt_bin_dir at the moment,
- # because the extracted archive also contains the opengl32sw
- # and the d3dcompiler dlls, which are copied not by this
- # function, but by the copydir below.
- copy_msvc_redist_files(vars, "{qt_bin_dir}".format(**vars))
-
- if artifacts:
- copydir("{qt_bin_dir}",
- "{st_build_dir}/{st_package_name}",
- filter=artifacts, recursive=False, vars=vars)
+ artifacts = qt_artifacts_permanent + qt_artifacts_egl
+ copy_qt_dependency_dlls(_vars, destination_qt_dir, artifacts)
# <qt>/bin/*.dll and Qt *.pdbs -> <setup>/{st_package_name} part two
# File filter to copy only debug or only release files.
if constrain_modules:
- qt_dll_patterns = ["Qt5" + x + "{}.dll" for x in constrain_modules]
+ qt_dll_patterns = [f"Qt6{x}{{}}.dll" for x in constrain_modules]
if copy_pdbs:
- qt_dll_patterns += ["Qt5" + x + "{}.pdb" for x in constrain_modules]
+ qt_dll_patterns += [f"Qt6{x}{{}}.pdb" for x in constrain_modules]
else:
- qt_dll_patterns = ["Qt5*{}.dll", "lib*{}.dll"]
+ qt_dll_patterns = ["Qt6*{}.dll", "lib*{}.dll"]
if copy_pdbs:
- qt_dll_patterns += ["Qt5*{}.pdb", "lib*{}.pdb"]
+ qt_dll_patterns += ["Qt6*{}.pdb", "lib*{}.pdb"]
def qt_build_config_filter(patterns, file_name, file_full_path):
release = [a.format('') for a in patterns]
@@ -356,74 +337,83 @@ def copy_qt_artifacts(self, copy_pdbs, vars):
# 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 pyside_build.qtinfo.build_type != 'debug_and_release':
if filter_match(file_name, release):
return True
return False
+ # Setup Paths
+ file_name = Path(file_name)
+ file_full_path = Path(file_full_path)
+
# 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
+ # match the PySide6 build type. So if PySide6 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
+ # actual library name (for example Qt6Gamepad.dll and
+ # Qt6Gamepadd.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. ["Qt6Cored", ".dll"]
+ file_base_name = file_name.stem
+ file_ext = file_name.suffix
# e.g. "/home/work/qt/qtbase/bin"
- file_path_dir_name = os.path.dirname(file_full_path)
- # e.g. "Qt5Coredd"
- maybe_debug_name = "{}d".format(file_base_name)
- if self.debug:
- filter = debug
+ file_path_dir_name = file_full_path.parent
+ # e.g. "Qt6Coredd"
+ maybe_debug_name = f"{file_base_name}d"
+ if pyside_build.debug:
+ _filter = debug
def predicate(path):
- return not os.path.exists(path)
+ return not path.exists()
else:
- filter = release
+ _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)
+ return path.exists()
+ # e.g. "/home/work/qt/qtbase/bin/Qt6Coredd.dll"
+ other_config_path = file_path_dir_name / (maybe_debug_name + file_ext)
- if (filter_match(file_name, filter) and predicate(other_config_path)):
+ 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}",
- "{st_build_dir}/{st_package_name}",
+ copydir("{qt_bin_dir}", destination_qt_dir,
file_filter_function=qt_dll_filter,
- recursive=False, vars=vars)
+ recursive=False, _vars=_vars)
if copy_plugins:
+ is_pypy = "pypy" in pyside_build.build_classifiers
# <qt>/plugins/* -> <setup>/{st_package_name}/plugins
+ plugins_target = f"{destination_qt_dir}/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}", "{st_build_dir}/{st_package_name}/plugins",
+ copydir("{qt_plugins_dir}", plugins_target,
file_filter_function=plugin_dll_filter,
- vars=vars)
+ _vars=_vars)
+ if not is_pypy:
+ copydir("{install_dir}/plugins/designer",
+ f"{plugins_target}/designer",
+ _filter=["*.dll"],
+ recursive=False,
+ _vars=_vars)
if copy_translations:
# <qt>/translations/* -> <setup>/{st_package_name}/translations
- copydir("{qt_translations_dir}",
- "{st_build_dir}/{st_package_name}/translations",
- filter=["*.qm", "*.pak"],
+ copydir("{qt_translations_dir}", f"{destination_qt_dir}/translations",
+ _filter=["*.qm", "*.pak"],
force=False,
- vars=vars)
+ _vars=_vars)
if copy_qml:
# <qt>/qml/* -> <setup>/{st_package_name}/qml
@@ -432,42 +422,40 @@ def copy_qt_artifacts(self, copy_pdbs, vars):
qml_ignore = [a.format('') for a in qml_ignore_patterns]
# Copy all files that are not dlls and pdbs (.qml, qmldir).
- copydir("{qt_qml_dir}", "{st_build_dir}/{st_package_name}/qml",
+ copydir("{qt_qml_dir}", f"{destination_qt_dir}/qml",
ignore=qml_ignore,
force=False,
recursive=True,
- vars=vars)
+ _vars=_vars)
if copy_pdbs:
qml_dll_patterns += [pdb_pattern]
qml_dll_filter = functools.partial(qt_build_config_filter, qml_dll_patterns)
# Copy all dlls (and possibly pdbs).
- copydir("{qt_qml_dir}", "{st_build_dir}/{st_package_name}/qml",
+ copydir("{qt_qml_dir}", f"{destination_qt_dir}/qml",
file_filter_function=qml_dll_filter,
force=False,
recursive=True,
- vars=vars)
+ _vars=_vars)
- if self.is_webengine_built(built_modules):
- copydir("{qt_prefix_dir}/resources",
- "{st_build_dir}/{st_package_name}/resources",
- filter=None,
+ if pyside_build.is_webengine_built(built_modules):
+ copydir("{qt_data_dir}/resources", f"{destination_qt_dir}/resources",
+ _filter=None,
recursive=False,
- vars=vars)
+ _vars=_vars)
- filter = 'QtWebEngineProcess{}.exe'.format(
- 'd' if self.debug else '')
- copydir("{qt_bin_dir}",
- "{st_build_dir}/{st_package_name}",
- filter=[filter],
- recursive=False, vars=vars)
+ _ext = "d" if pyside_build.debug else ""
+ _filter = [f"QtWebEngineProcess{_ext}.exe"]
+ copydir("{qt_bin_dir}", destination_qt_dir,
+ _filter=_filter,
+ recursive=False, _vars=_vars)
if copy_qt_conf:
# Copy the qt.conf file to prefix dir.
- copyfile("{build_dir}/pyside2/{st_package_name}/qt.conf",
- "{st_build_dir}/{st_package_name}",
- vars=vars)
+ copyfile(f"{{build_dir}}/{PYSIDE}/{{st_package_name}}/qt.conf",
+ destination_qt_dir,
+ _vars=_vars)
if copy_clang:
- self.prepare_standalone_clang(is_win=True)
+ pyside_build.prepare_standalone_clang(is_win=True)
diff --git a/build_scripts/qp5_tool.py b/build_scripts/qfp_tool.py
index 9fc37a99b..abaf48fc8 100644
--- a/build_scripts/qp5_tool.py
+++ b/build_scripts/qfp_tool.py
@@ -1,54 +1,17 @@
-#############################################################################
-##
-## Copyright (C) 2019 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
-
-from __future__ import print_function
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-from argparse import ArgumentParser, RawTextHelpFormatter
import datetime
-from enum import Enum
import os
import re
import subprocess
import sys
import time
import warnings
-
+from argparse import ArgumentParser, RawTextHelpFormatter
+from enum import Enum, auto
+from pathlib import Path
+from typing import List
DESC = """
Utility script for working with Qt for Python.
@@ -56,9 +19,9 @@ Utility script for working with Qt for Python.
Feel free to extend!
Typical Usage:
-Update and build a repository: python qp5_tool -p -b
+Update and build a repository: python qfp_tool -p -b
-qp5_tool.py uses a configuration file "%CONFIGFILE%"
+qfp_tool.py uses a configuration file "%CONFIGFILE%"
in the format key=value.
It is possible to use repository-specific values by adding a key postfixed by
@@ -68,6 +31,8 @@ Modules-pyside-setup512=Core,Gui,Widgets,Network,Test
Configuration keys:
Acceleration Incredibuild or unset
BuildArguments Arguments to setup.py
+Generator Generator to be used for CMake. Currently, only Ninja is
+ supported.
Jobs Number of jobs to be run simultaneously
Modules Comma separated list of modules to be built
(for --module-subset=)
@@ -93,54 +58,65 @@ class BuildMode(Enum):
MAKE = 3
-DEFAULT_BUILD_ARGS = ['--build-tests', '--skip-docs', '--quiet']
+class UnityMode(Enum):
+ DEFAULT = auto()
+ ENABLE = auto()
+ DISABLE = auto()
+
+
+DISABLE_UNITY_OPTION = "--no-unity"
+LOG_LEVEL_OPTION = "--log-level"
+DEFAULT_BUILD_ARGS = ['--build-tests', '--skip-docs', LOG_LEVEL_OPTION, "quiet"]
IS_WINDOWS = sys.platform == 'win32'
INCREDIBUILD_CONSOLE = 'BuildConsole' if IS_WINDOWS else '/opt/incredibuild/bin/ib_console'
# Config file keys
ACCELERATION_KEY = 'Acceleration'
BUILDARGUMENTS_KEY = 'BuildArguments'
+GENERATOR_KEY = 'Generator'
JOBS_KEY = 'Jobs'
MODULES_KEY = 'Modules'
PYTHON_KEY = 'Python'
DEFAULT_MODULES = "Core,Gui,Widgets,Network,Test,Qml,Quick,Multimedia,MultimediaWidgets"
-DEFAULT_CONFIG_FILE = "Modules={}\n".format(DEFAULT_MODULES)
+DEFAULT_CONFIG_FILE = f"Modules={DEFAULT_MODULES}\n"
build_mode = BuildMode.NONE
opt_dry_run = False
+opt_verbose = False
+opt_unity_mode = UnityMode.DEFAULT
-def which(needle):
+def which(needle: str):
"""Perform a path search"""
needles = [needle]
if IS_WINDOWS:
for ext in ("exe", "bat", "cmd"):
- needles.append("{}.{}".format(needle, ext))
+ needles.append(f"{needle}.{ext}")
for path in os.environ.get("PATH", "").split(os.pathsep):
for n in needles:
- binary = os.path.join(path, n)
- if os.path.isfile(binary):
+ binary = Path(path) / n
+ if binary.is_file():
return binary
return None
-def command_log_string(args, dir):
- result = '[{}]'.format(os.path.basename(dir))
+def command_log_string(args: List[str], directory: Path):
+ result = f'[{directory.name}]'
for arg in args:
- result += ' "{}"'.format(arg) if ' ' in arg else ' {}'.format(arg)
+ result += f' "{arg}"' if ' ' in arg else f' {arg}'
return result
-def execute(args):
+def execute(args: List[str]):
"""Execute a command and print to log"""
- log_string = command_log_string(args, os.getcwd())
+ log_string = command_log_string(args, Path.cwd())
print(log_string)
if opt_dry_run:
return
exit_code = subprocess.call(args)
if exit_code != 0:
- raise RuntimeError('FAIL({}): {}'.format(exit_code, log_string))
+ raise RuntimeError(f'FAIL({exit_code}): {log_string}')
def run_process_output(args):
@@ -156,9 +132,6 @@ def run_git(args):
"""Run git in the current directory and its submodules"""
args.insert(0, git) # run in repo
execute(args) # run for submodules
- module_args = [git, "submodule", "foreach"]
- module_args.extend(args)
- execute(module_args)
def expand_reference(cache_dict, value):
@@ -192,7 +165,7 @@ def edit_config_file():
exit_code = subprocess.call([editor(), config_file])
except Exception as e:
reason = str(e)
- print('Unable to launch: {}: {}'.format(editor(), reason))
+ print(f'Unable to launch: {editor()}: {reason}')
return exit_code
@@ -208,9 +181,10 @@ def read_config_file(file_name):
keyPattern = re.compile(r'^\s*([A-Za-z0-9\_\-]+)\s*=\s*(.*)$')
with open(file_name) as f:
while True:
- line = f.readline().rstrip()
+ line = f.readline()
if not line:
break
+ line = line.rstrip()
match = keyPattern.match(line)
if match:
key = match.group(1)
@@ -223,13 +197,13 @@ def read_config_file(file_name):
def read_config(key):
"""
- Read a value from the '$HOME/.qp5_tool' configuration file. When given
+ Read a value from the '$HOME/.qfp_tool' configuration file. When given
a key 'key' for the repository directory '/foo/qt-5', check for the
repo-specific value 'key-qt5' and then for the general 'key'.
"""
if not config_dict:
read_config_file(config_file)
- repo_value = config_dict.get(key + '-' + base_dir)
+ repo_value = config_dict.get(f"{key}-{base_dir}")
return repo_value if repo_value else config_dict.get(key)
@@ -262,18 +236,33 @@ def read_config_build_arguments():
def read_config_modules_argument():
value = read_config(MODULES_KEY)
if value and value != '' and value != 'all':
- return '--module-subset=' + value
+ return f"--module-subset={value}"
return None
-def read_config_python_binary():
+def read_config_python_binary() -> str:
binary = read_config(PYTHON_KEY)
- if binary:
- return binary
- return 'python3' if which('python3') else 'python'
+ virtual_env = os.environ.get('VIRTUAL_ENV')
+ if not binary:
+ # Use 'python3' unless virtualenv is set
+ use_py3 = not virtual_env and which('python3')
+ binary = 'python3' if use_py3 else 'python'
+ binary = Path(binary)
+ if not binary.is_absolute():
+ abs_path = which(str(binary))
+ if abs_path:
+ binary = abs_path
+ else:
+ warnings.warn(f'Unable to find "{binary}"', RuntimeWarning)
+ if virtual_env:
+ if not str(binary).startswith(virtual_env):
+ w = f'Python "{binary}" is not under VIRTUAL_ENV "{virtual_env}"'
+ warnings.warn(w, RuntimeWarning)
+ return str(binary)
-def get_config_file(base_name):
+def get_config_file(base_name) -> Path:
+ global user
home = os.getenv('HOME')
if IS_WINDOWS:
# Set a HOME variable on Windows such that scp. etc.
@@ -282,18 +271,18 @@ def get_config_file(base_name):
home = os.getenv('HOMEDRIVE') + os.getenv('HOMEPATH')
os.environ['HOME'] = home
user = os.getenv('USERNAME')
- config_file = os.path.join(os.getenv('APPDATA'), base_name)
+ config_file = Path(os.getenv('APPDATA')) / base_name
else:
user = os.getenv('USER')
- config_dir = os.path.join(home, '.config')
- if os.path.exists(config_dir):
- config_file = os.path.join(config_dir, base_name)
+ config_dir = Path(home) / '.config'
+ if config_dir.exists():
+ config_file = config_dir / base_name
else:
- config_file = os.path.join(home, '.' + base_name)
+ config_file = Path(home) / f".{base_name}"
return config_file
-def build(target):
+def build(target: str):
"""Run configure and build steps"""
start_time = time.time()
@@ -301,14 +290,28 @@ def build(target):
acceleration = read_acceleration_config()
if not IS_WINDOWS and acceleration == Acceleration.INCREDIBUILD:
arguments.append(INCREDIBUILD_CONSOLE)
- arguments.append('--avoid') # caching, v0.96.74
+ arguments.appendh('--avoid') # caching, v0.96.74
arguments.extend([read_config_python_binary(), 'setup.py', target])
- arguments.extend(read_config_build_arguments())
+ build_arguments = read_config_build_arguments()
+ if opt_verbose and LOG_LEVEL_OPTION in build_arguments:
+ i = build_arguments.index(LOG_LEVEL_OPTION)
+ del build_arguments[i]
+ del build_arguments[i]
+ arguments.extend(build_arguments)
+ if opt_unity_mode != UnityMode.DEFAULT:
+ unity_disabled = DISABLE_UNITY_OPTION in build_arguments
+ if opt_unity_mode == UnityMode.ENABLE and unity_disabled:
+ arguments.remove(DISABLE_UNITY_OPTION)
+ elif opt_unity_mode == UnityMode.DISABLE and not unity_disabled:
+ arguments.append(DISABLE_UNITY_OPTION)
+ generator = read_config(GENERATOR_KEY)
+ if generator != 'Ninja':
+ arguments.extend(['--make-spec', 'ninja'])
jobs = read_int_config(JOBS_KEY)
if jobs > 1:
arguments.extend(['-j', str(jobs)])
if build_mode != BuildMode.BUILD:
- arguments.extend(['--reuse-build', '--ignore-git'])
+ arguments.append('--reuse-build')
if build_mode != BuildMode.RECONFIGURE:
arguments.append('--skip-cmake')
modules = read_config_modules_argument()
@@ -316,24 +319,34 @@ def build(target):
arguments.append(modules)
if IS_WINDOWS and acceleration == Acceleration.INCREDIBUILD:
arg_string = ' '.join(arguments)
- arguments = [INCREDIBUILD_CONSOLE, '/command={}'.format(arg_string)]
+ arguments = [INCREDIBUILD_CONSOLE, f'/command={arg_string}']
execute(arguments)
elapsed_time = int(time.time() - start_time)
- print('--- Done({}s) ---'.format(elapsed_time))
+ print(f'--- Done({elapsed_time}s) ---')
+
+
+def build_base_docs():
+ arguments = [read_config_python_binary(), "setup.py", "build_base_docs", "--log-level",
+ "quiet"]
+ for build_arg in read_config_build_arguments():
+ if build_arg.startswith("--qt-src-dir="):
+ arguments.append(build_arg)
+ break
+ execute(arguments)
def run_tests():
"""Run tests redirected into a log file with a time stamp"""
logfile_name = datetime.datetime.today().strftime("test_%Y%m%d_%H%M.txt")
binary = sys.executable
- command = '"{}" testrunner.py test > {}'.format(binary, logfile_name)
- print(command_log_string([command], os.getcwd()))
+ command = f'"{binary}" testrunner.py test > {logfile_name}'
+ print(command_log_string([command], Path.cwd()))
start_time = time.time()
result = 0 if opt_dry_run else os.system(command)
elapsed_time = int(time.time() - start_time)
- print('--- Done({}s) ---'.format(elapsed_time))
+ print(f'--- Done({elapsed_time}s) ---')
return result
@@ -358,7 +371,15 @@ def create_argument_parser(desc):
help='cmake + Make (continue broken build)')
parser.add_argument('--test', '-t', action='store_true',
help='Run tests')
+ parser.add_argument('--Documentation', '-D', action='store_true',
+ help='Run build_base_docs')
parser.add_argument('--version', '-v', action='version', version='%(prog)s 1.0')
+ parser.add_argument('--verbose', '-V', action='store_true',
+ help='Turn off --quiet specified in build arguments')
+ parser.add_argument('--unity', '-u', action='store_true',
+ help='Force unity build')
+ parser.add_argument('--no-unity', action='store_true',
+ help='Turn off --unity specified in build arguments')
return parser
@@ -368,10 +389,16 @@ if __name__ == '__main__':
config_file = None
user = None
- config_file = get_config_file('qp5_tool.conf')
- argument_parser = create_argument_parser(DESC.replace('%CONFIGFILE%', config_file))
+ config_file = get_config_file('qfp_tool.conf')
+ argument_parser = create_argument_parser(DESC.replace('%CONFIGFILE%', str(config_file)))
options = argument_parser.parse_args()
opt_dry_run = options.dry_run
+ opt_verbose = options.verbose
+
+ if options.unity:
+ opt_unity_mode = UnityMode.ENABLE
+ elif options.no_unity:
+ opt_unity_mode = UnityMode.DISABLE
if options.edit:
sys.exit(edit_config_file())
@@ -383,29 +410,30 @@ if __name__ == '__main__':
elif options.Make:
build_mode = BuildMode.RECONFIGURE
- if build_mode == BuildMode.NONE and not (options.clean or options.reset
- or options.pull or options.test):
+ if build_mode == BuildMode.NONE and not (options.clean or options.reset or options.pull
+ or options.Documentation or options.test):
argument_parser.print_help()
sys.exit(0)
- git = which('git')
- if git is None:
+ git = 'git'
+ if which(git) is None:
warnings.warn('Unable to find git', RuntimeWarning)
sys.exit(-1)
- if not os.path.exists(config_file):
+ if not config_file.exists():
print('Create initial config file ', config_file, " ..")
with open(config_file, 'w') as f:
f.write(DEFAULT_CONFIG_FILE.format(' '.join(DEFAULT_BUILD_ARGS)))
- while not os.path.exists('.gitmodules'):
- cwd = os.getcwd()
- if cwd == '/' or (IS_WINDOWS and len(cwd) < 4):
+ while not Path(".git").exists():
+ cwd = Path.cwd()
+ cwd_s = os.fspath(cwd)
+ if cwd_s == '/' or (IS_WINDOWS and len(cwd_s) < 4):
warnings.warn('Unable to find git root', RuntimeWarning)
sys.exit(-1)
- os.chdir(os.path.dirname(cwd))
+ os.chdir(cwd.parent)
- base_dir = os.path.basename(os.getcwd())
+ base_dir = Path.cwd().name
if options.clean:
run_git(['clean', '-dxf'])
@@ -420,6 +448,9 @@ if __name__ == '__main__':
target = 'build' if options.no_install else 'install'
build(target)
+ if options.Documentation:
+ build_base_docs()
+
if options.test:
sys.exit(run_tests())
diff --git a/build_scripts/qtinfo.py b/build_scripts/qtinfo.py
index 4dc976360..1eb7c4909 100644
--- a/build_scripts/qtinfo.py
+++ b/build_scripts/qtinfo.py
@@ -1,241 +1,261 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
import os
-import sys
-import re
import subprocess
-from distutils.spawn import find_executable
+from pathlib import Path
+
+from .utils import (configure_cmake_project, parse_cmake_project_message_info,
+ platform_cmake_options)
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._init_properties()
-
- def get_qmake_command(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 get_version(self):
- return self.get_property("QT_VERSION")
-
- def get_bins_path(self):
- return self.get_property("QT_INSTALL_BINS")
-
- def get_libs_path(self):
- return self.get_property("QT_INSTALL_LIBS")
-
- def get_libs_execs_path(self):
- return self.get_property("QT_INSTALL_LIBEXECS")
-
- def get_plugins_path(self):
- return self.get_property("QT_INSTALL_PLUGINS")
-
- def get_prefix_path(self):
- return self.get_property("QT_INSTALL_PREFIX")
-
- def get_imports_path(self):
- return self.get_property("QT_INSTALL_IMPORTS")
-
- def get_translations_path(self):
- return self.get_property("QT_INSTALL_TRANSLATIONS")
-
- def get_headers_path(self):
- return self.get_property("QT_INSTALL_HEADERS")
-
- def get_docs_path(self):
- return self.get_property("QT_INSTALL_DOCS")
-
- def get_qml_path(self):
- return self.get_property("QT_INSTALL_QML")
-
- def get_macos_deployment_target(self):
- """ Return value is a macOS version or None. """
- return self.get_property("QMAKE_MACOSX_DEPLOYMENT_TARGET")
-
- def get_build_type(self):
- """
- Return value is either debug, release, debug_release, or None.
- """
- return self.get_property("BUILD_TYPE")
-
- def get_src_dir(self):
- """ Return path to Qt src dir or None.. """
- return self.get_property("QT_INSTALL_PREFIX/src")
-
- def get_property(self, prop_name):
- if prop_name not in self._query_dict:
- return None
- return self._query_dict[prop_name]
-
- def get_properties(self):
- return self._query_dict
-
- def get_mkspecs_variables(self):
- return self._mkspecs_dict
-
- def _get_qmake_output(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 ""
- if sys.version_info >= (3,):
- output = str(output, 'ascii').strip()
- else:
- output = output.strip()
- return output
-
- def _parse_query_properties(self, process_output):
- props = {}
- if not process_output:
+ _instance = None # singleton helpers
+
+ def __new__(cls): # __new__ always a classmethod
+ if not QtInfo._instance:
+ QtInfo._instance = QtInfo.__QtInfo()
+ return QtInfo._instance
+
+ def __getattr__(self, name):
+ return getattr(self._instance, name)
+
+ def __setattr__(self, name):
+ return setattr(self._instance, name)
+
+ class __QtInfo: # Python singleton
+ def __init__(self):
+ self._qtpaths_command = None
+ self._cmake_command = None
+ self._qmake_command = None
+ self._force_qmake = False
+ self._use_cmake = False
+ self._qt_target_path = None
+ self._cmake_toolchain_file = None
+ # Dict to cache qmake values.
+ self._query_dict = {}
+
+ def setup(self, qtpaths, cmake, qmake, force_qmake, use_cmake, qt_target_path,
+ cmake_toolchain_file):
+ self._qtpaths_command = qtpaths
+ self._cmake_command = cmake
+ self._qmake_command = qmake
+ self._force_qmake = force_qmake
+ self._use_cmake = use_cmake
+ self._qt_target_path = qt_target_path
+ self._cmake_toolchain_file = cmake_toolchain_file
+
+ @property
+ def qmake_command(self):
+ return self._qmake_command
+
+ @property
+ def qtpaths_command(self):
+ return self._qtpaths_command
+
+ @property
+ def version(self):
+ return self.get_property("QT_VERSION")
+
+ @property
+ def version_tuple(self):
+ return tuple(map(int, self.version.split(".")))
+
+ @property
+ def bins_dir(self):
+ return self.get_property("QT_INSTALL_BINS")
+
+ @property
+ def data_dir(self):
+ return self.get_property("QT_INSTALL_DATA")
+
+ @property
+ def libs_dir(self):
+ return self.get_property("QT_INSTALL_LIBS")
+
+ @property
+ def module_json_files_dir(self):
+ # FIXME: Use INSTALL_DESCRIPTIONSDIR once QTBUG-116983 is done.
+ result = Path(self.arch_data) / "modules"
+ return os.fspath(result)
+
+ @property
+ def metatypes_dir(self):
+ parent = self.arch_data if self.version_tuple >= (6, 5, 0) else self.libs_dir
+ return os.fspath(Path(parent) / "metatypes")
+
+ @property
+ def lib_execs_dir(self):
+ return self.get_property("QT_INSTALL_LIBEXECS")
+
+ @property
+ def plugins_dir(self):
+ return self.get_property("QT_INSTALL_PLUGINS")
+
+ @property
+ def prefix_dir(self):
+ return self.get_property("QT_INSTALL_PREFIX")
+
+ @property
+ def arch_data(self):
+ return self.get_property("QT_INSTALL_ARCHDATA")
+
+ @property
+ def imports_dir(self):
+ return self.get_property("QT_INSTALL_IMPORTS")
+
+ @property
+ def translations_dir(self):
+ return self.get_property("QT_INSTALL_TRANSLATIONS")
+
+ @property
+ def headers_dir(self):
+ return self.get_property("QT_INSTALL_HEADERS")
+
+ @property
+ def docs_dir(self):
+ return self.get_property("QT_INSTALL_DOCS")
+
+ @property
+ def qml_dir(self):
+ return self.get_property("QT_INSTALL_QML")
+
+ @property
+ def macos_min_deployment_target(self):
+ """ Return value is a macOS version or None. """
+ return self.get_property("QMAKE_MACOSX_DEPLOYMENT_TARGET")
+
+ @property
+ def build_type(self):
+ """
+ Return value is either debug, release, debug_release, or None.
+ """
+ return self.get_property("BUILD_TYPE")
+
+ @property
+ def src_dir(self):
+ """ Return path to Qt src dir or None.. """
+ return self.get_property("QT_INSTALL_PREFIX/src")
+
+ def get_property(self, prop_name):
+ if not self._query_dict:
+ self._get_query_properties()
+ self._get_other_properties()
+ if prop_name not in self._query_dict:
+ return None
+ return self._query_dict[prop_name]
+
+ def _get_qtpaths_output(self, args_list=None, cwd=None):
+ if args_list is None:
+ args_list = []
+ assert self._qtpaths_command
+ cmd = [str(self._qtpaths_command)]
+ cmd.extend(args_list)
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=False,
+ cwd=cwd, universal_newlines=True)
+ output, error = proc.communicate()
+ proc.wait()
+ if proc.returncode != 0:
+ raise RuntimeError(f"Could not run {self._qtpaths_command}: {error}")
+ return output
+
+ # FIXME PYSIDE7: Remove qmake handling
+ def _get_qmake_output(self, args_list=None, cwd=None):
+ if args_list is None:
+ args_list = []
+ assert self._qmake_command
+ cmd = [self._qmake_command]
+ cmd.extend(args_list)
+ proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=False,
+ cwd=cwd)
+ output = proc.communicate()[0]
+ proc.wait()
+ if proc.returncode != 0:
+ return ""
+ output = str(output, "ascii").strip()
+ return output
+
+ def _parse_query_properties(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
- 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 _get_query_properties(self):
- output = self._get_qmake_output(['-query'])
- self._query_dict = self._parse_query_properties(output)
-
- def _parse_qt_build_type(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 _get_other_properties(self):
- # Get the src property separately, because it is not returned by
- # qmake unless explicitly specified.
- key = 'QT_INSTALL_PREFIX/src'
- result = self._get_qmake_output(['-query', key])
- self._query_dict[key] = result
-
- # Get mkspecs variables and cache them.
- self._get_qmake_mkspecs_variables()
-
- # 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._parse_qt_build_type()
- if build_type:
- self._query_dict['BUILD_TYPE'] = build_type
-
- def _init_properties(self):
- self._get_query_properties()
- self._get_other_properties()
-
- def _get_qmake_mkspecs_variables(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.
- qmake_output = self._get_qmake_output(['-E', temp_file_name])
- lines = [s.strip() for s in qmake_output.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(get_version)
- bins_dir = property(get_bins_path)
- libs_dir = property(get_libs_path)
- lib_execs_dir = property(get_libs_execs_path)
- plugins_dir = property(get_plugins_path)
- prefix_dir = property(get_prefix_path)
- qmake_command = property(get_qmake_command)
- imports_dir = property(get_imports_path)
- translations_dir = property(get_translations_path)
- headers_dir = property(get_headers_path)
- docs_dir = property(get_docs_path)
- qml_dir = property(get_qml_path)
- macos_min_deployment_target = property(get_macos_deployment_target)
- build_type = property(get_build_type)
- src_dir = property(get_src_dir)
+
+ def _get_query_properties(self):
+ if self._use_cmake:
+ setup_script_dir = Path.cwd()
+ sources_dir = setup_script_dir / "sources"
+ qt_target_info_dir = sources_dir / "shiboken6" / "config.tests" / "target_qt_info"
+ qt_target_info_dir = os.fspath(qt_target_info_dir)
+ config_tests_dir = setup_script_dir / "build" / "config.tests"
+ config_tests_dir = os.fspath(config_tests_dir)
+
+ cmake_cache_args = []
+ if self._cmake_toolchain_file:
+ cmake_cache_args.append(("CMAKE_TOOLCHAIN_FILE", self._cmake_toolchain_file))
+
+ if self._qt_target_path:
+ cmake_cache_args.append(("QFP_QT_TARGET_PATH", self._qt_target_path))
+ qt_target_info_output = configure_cmake_project(
+ qt_target_info_dir,
+ self._cmake_command,
+ temp_prefix_build_path=config_tests_dir,
+ cmake_cache_args=cmake_cache_args)
+ qt_target_info = parse_cmake_project_message_info(qt_target_info_output)
+ self._query_dict = qt_target_info['qt_info']
+ else:
+ if self._force_qmake:
+ output = self._get_qmake_output(["-query"])
+ else:
+ output = self._get_qtpaths_output(["--qt-query"])
+ self._query_dict = self._parse_query_properties(output)
+
+ def _get_other_properties(self):
+ # Get the src property separately, because it is not returned by
+ # qmake unless explicitly specified.
+ key = "QT_INSTALL_PREFIX/src"
+ if not self._use_cmake:
+ if self._force_qmake:
+ result = self._get_qmake_output(["-query", key])
+ else:
+ result = self._get_qtpaths_output(["--qt-query", key])
+ self._query_dict[key] = result
+
+ # Get mkspecs variables and cache them.
+ # FIXME Python 3.9 self._query_dict |= other_dict
+ for key, value in self._get_cmake_mkspecs_variables().items():
+ self._query_dict[key] = value
+
+ def _get_cmake_mkspecs_variables(self):
+ setup_script_dir = Path.cwd()
+ sources_dir = setup_script_dir / "sources"
+ qt_target_mkspec_dir = sources_dir / "shiboken6" / "config.tests" / "target_qt_mkspec"
+ qt_target_mkspec_dir = qt_target_mkspec_dir.as_posix()
+ config_tests_dir = setup_script_dir / "build" / "config.tests"
+ config_tests_dir = config_tests_dir.as_posix()
+
+ cmake_cache_args = []
+ if self._cmake_toolchain_file:
+ cmake_cache_args.append(("CMAKE_TOOLCHAIN_FILE", self._cmake_toolchain_file))
+ if self._qt_target_path:
+ cmake_cache_args.append(("QFP_QT_TARGET_PATH", self._qt_target_path))
+ else:
+ qt_prefix = Path(self.prefix_dir).as_posix()
+ cmake_cache_args.append(("CMAKE_PREFIX_PATH", qt_prefix))
+
+ cmake_cache_args.extend(platform_cmake_options(as_tuple_list=True))
+ qt_target_mkspec_output = configure_cmake_project(
+ qt_target_mkspec_dir,
+ self._cmake_command,
+ temp_prefix_build_path=config_tests_dir,
+ cmake_cache_args=cmake_cache_args)
+
+ qt_target_mkspec_info = parse_cmake_project_message_info(qt_target_mkspec_output)
+ qt_target_mkspec_info = qt_target_mkspec_info['qt_info']
+
+ return qt_target_mkspec_info
diff --git a/build_scripts/setup_runner.py b/build_scripts/setup_runner.py
index 1a7317e4d..5d0466247 100644
--- a/build_scripts/setup_runner.py
+++ b/build_scripts/setup_runner.py
@@ -1,53 +1,21 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-import sys
import os
+import sys
+import tempfile
import textwrap
+import logging
+
+from pathlib import Path
+from setuptools import setup
from build_scripts.config import config
-from build_scripts.main import get_package_version, get_setuptools_extension_modules
-from build_scripts.main import cmd_class_dict
-from build_scripts.options import OPTION
+from build_scripts.main import (cmd_class_dict, get_package_version,
+ get_setuptools_extension_modules)
+from build_scripts.options import ADDITIONAL_OPTIONS, OPTION
from build_scripts.utils import run_process
-
-from setuptools import setup
+from build_scripts.log import log, LogLevel
class SetupRunner(object):
@@ -59,38 +27,140 @@ class SetupRunner(object):
self.orig_argv = orig_argv
self.sub_argv = list(orig_argv)
- self.setup_script_dir = os.getcwd()
+ self.setup_script_dir = Path.cwd()
@staticmethod
def cmd_line_argument_is_in_args(argument, args):
""" Check if command line argument was passed in args. """
- return any(arg for arg in list(args) if "--" + argument in arg)
+ return any(arg for arg in list(args) if f"--{argument}" in arg)
+
+ @staticmethod
+ def get_cmd_line_argument_in_args(argument, args):
+ """ Gets the value of a cmd line argument passed in args. """
+ for arg in list(args):
+ if f"--{argument}" in arg:
+ prefix = f"--{argument}"
+ prefix_len = len(prefix) + 1
+ return arg[prefix_len:]
+ return None
@staticmethod
def remove_cmd_line_argument_in_args(argument, args):
""" Remove command line argument from args. """
- return [arg for arg in list(args) if "--" + argument not in arg]
+ return [arg for arg in list(args) if f"--{argument}" not in arg]
@staticmethod
def construct_cmd_line_argument(name, value=None):
""" Constructs a command line argument given name and value. """
if not value:
- return "--{}".format(name)
- return "--{}={}".format(name, value)
+ return f"--{name}"
+ return f"--{name}={value}"
@staticmethod
def construct_internal_build_type_cmd_line_argument(internal_build_type):
return SetupRunner.construct_cmd_line_argument("internal-build-type", internal_build_type)
- def add_setup_internal_invocation(self, build_type, reuse_build=False):
- """ Enqueues a script sub-invocation to be executed later. """
+ def enqueue_setup_internal_invocation(self, setup_cmd):
+ self.invocations_list.append(setup_cmd)
+
+ def add_setup_internal_invocation(self, build_type, reuse_build=False, extra_args=None):
+ setup_cmd = self.new_setup_internal_invocation(build_type, reuse_build, extra_args)
+ self.enqueue_setup_internal_invocation(setup_cmd)
+
+ def new_setup_internal_invocation(self, build_type,
+ reuse_build=False,
+ extra_args=None,
+ replace_command_with=None):
+ """ Creates a script sub-invocation to be executed later. """
internal_build_type_arg = self.construct_internal_build_type_cmd_line_argument(build_type)
- setup_cmd = [sys.executable] + self.sub_argv + [internal_build_type_arg]
+
+ command_index = 0
+ command = self.sub_argv[command_index]
+ if command == 'setup.py' and len(self.sub_argv) > 1:
+ command_index = 1
+ command = self.sub_argv[command_index]
+
+ # Make a copy
+ modified_argv = list(self.sub_argv)
+
+ if replace_command_with:
+ modified_argv[command_index] = replace_command_with
+
+ setup_cmd = [sys.executable] + modified_argv + [internal_build_type_arg]
+
+ if extra_args:
+ for (name, value) in extra_args:
+ setup_cmd.append(self.construct_cmd_line_argument(name, value))
# Add --reuse-build option if requested and not already present.
- if reuse_build and not self.cmd_line_argument_is_in_args("reuse-build", self.sub_argv):
+ if (reuse_build and command in ('bdist_wheel', 'build', 'build_base_docs', 'install')
+ and not self.cmd_line_argument_is_in_args("reuse-build", modified_argv)):
setup_cmd.append(self.construct_cmd_line_argument("reuse-build"))
- self.invocations_list.append(setup_cmd)
+ return setup_cmd
+
+ def add_host_tools_setup_internal_invocation(self, initialized_config):
+ extra_args = []
+ extra_host_args = []
+
+ # When cross-compiling, build the host shiboken generator tool
+ # only if a path to an existing one was not provided.
+ if not self.cmd_line_argument_is_in_args("shiboken-host-path", self.sub_argv):
+ handle, initialized_config.shiboken_host_query_path = tempfile.mkstemp()
+ os.close(handle)
+
+ # Tell the setup process to create a file with the location
+ # of the installed host shiboken as its contents.
+ extra_host_args.append(
+ ("internal-cmake-install-dir-query-file-path",
+ initialized_config.shiboken_host_query_path))
+
+ # Tell the other setup invocations to read that file and use
+ # the read path as the location of the host shiboken.
+ extra_args.append(
+ ("internal-shiboken-host-path-query-file",
+ initialized_config.shiboken_host_query_path)
+ )
+
+ # This is specifying shiboken_module_option_name
+ # instead of shiboken_generator_option_name, but it will
+ # actually build the generator.
+ host_cmd = self.new_setup_internal_invocation(
+ initialized_config.shiboken_module_option_name,
+ extra_args=extra_host_args,
+ replace_command_with="build")
+
+ # To build the host tools, we reuse the initial target
+ # command line arguments, but we remove some options that
+ # don't make sense for the host build.
+
+ # Drop the toolchain arg.
+ host_cmd = self.remove_cmd_line_argument_in_args("cmake-toolchain-file",
+ host_cmd)
+
+ # Drop the target plat-name arg if there is one.
+ if self.cmd_line_argument_is_in_args("plat-name", host_cmd):
+ host_cmd = self.remove_cmd_line_argument_in_args("plat-name", host_cmd)
+
+ # Drop the python-target-path arg if there is one.
+ if self.cmd_line_argument_is_in_args("python-target-path", host_cmd):
+ host_cmd = self.remove_cmd_line_argument_in_args("python-target-path", host_cmd)
+
+ # Drop the target build-tests arg if there is one.
+ if self.cmd_line_argument_is_in_args("build-tests", host_cmd):
+ host_cmd = self.remove_cmd_line_argument_in_args("build-tests", host_cmd)
+
+ # Make sure to pass the qt host path as the target path
+ # when doing the host build. And make sure to remove any
+ # existing qt target path.
+ if self.cmd_line_argument_is_in_args("qt-host-path", host_cmd):
+ qt_host_path = self.get_cmd_line_argument_in_args("qt-host-path", host_cmd)
+ host_cmd = self.remove_cmd_line_argument_in_args("qt-host-path", host_cmd)
+ host_cmd = self.remove_cmd_line_argument_in_args("qt-target-path", host_cmd)
+ host_cmd.append(self.construct_cmd_line_argument("qt-target-path",
+ qt_host_path))
+
+ self.enqueue_setup_internal_invocation(host_cmd)
+ return extra_args
def run_setup(self):
"""
@@ -101,6 +171,13 @@ class SetupRunner(object):
will run setuptools.setup().
"""
+ # PYSIDE-1746: We prevent the generation of .pyc/.pyo files during installation.
+ # These files are generated anyway on their import.
+ sys.dont_write_bytecode = True
+ qt_install_path = OPTION["QTPATHS"]
+ if qt_install_path:
+ qt_install_path = Path(qt_install_path).parents[1]
+
# Prepare initial config.
config.init_config(build_type=OPTION["BUILD_TYPE"],
internal_build_type=OPTION["INTERNAL_BUILD_TYPE"],
@@ -108,14 +185,25 @@ class SetupRunner(object):
package_version=get_package_version(),
ext_modules=get_setuptools_extension_modules(),
setup_script_dir=self.setup_script_dir,
- quiet=OPTION["QUIET"])
+ cmake_toolchain_file=OPTION["CMAKE_TOOLCHAIN_FILE"],
+ log_level=OPTION["LOG_LEVEL"],
+ qt_install_path=qt_install_path)
+
+ # Enable logging for both the top-level invocation of setup.py
+ # as well as for child invocations. We we now use
+ if OPTION["LOG_LEVEL"] == LogLevel.VERBOSE:
+ log.setLevel(logging.DEBUG)
+ elif OPTION["LOG_LEVEL"] == LogLevel.QUIET:
+ log.setLevel(logging.ERROR)
+ elif OPTION["LOG_LEVEL"] == LogLevel.INFO:
+ log.setLevel(logging.INFO)
# This is an internal invocation of setup.py, so start actual
# build.
if config.is_internal_invocation():
if config.internal_build_type not in config.get_allowed_internal_build_values():
- raise RuntimeError("Invalid '{}' option given to --internal-build-type. "
- .format(config.internal_build_type))
+ raise RuntimeError(f"Invalid '{config.internal_build_type}' option given to "
+ "--internal-build-type. ")
self.run_setuptools_setup()
return
@@ -123,19 +211,37 @@ class SetupRunner(object):
# modules we will build and depending on that, call setup.py
# multiple times with different arguments.
if config.build_type not in config.get_allowed_top_level_build_values():
- raise RuntimeError("Invalid '{}' option given to --build-type. "
- .format(config.build_type))
+ raise RuntimeError(f"Invalid '{config.build_type}' option given to --build-type. ")
- # Build everything: shiboken2, shiboken2-generator and PySide2.
- if config.is_top_level_build_all():
- self.add_setup_internal_invocation(config.shiboken_module_option_name)
+ # Build everything: shiboken6, shiboken6-generator and PySide6.
+ help_requested = '--help' in self.sub_argv or '-h' in self.sub_argv
+
+ if help_requested:
+ self.add_setup_internal_invocation(config.pyside_option_name)
+
+ elif config.is_top_level_build_all():
+ extra_args = []
+
+ # extra_args might contain the location of the built host
+ # shiboken, which needs to be passed to the other
+ # target invocations.
+ if config.is_cross_compile():
+ extra_args = self.add_host_tools_setup_internal_invocation(config)
+
+ self.add_setup_internal_invocation(
+ config.shiboken_module_option_name,
+ extra_args=extra_args)
# Reuse the shiboken build for the generator package instead
# of rebuilding it again.
- self.add_setup_internal_invocation(config.shiboken_generator_option_name,
- reuse_build=True)
+ # Don't build it in a cross-build though.
+ if not config.is_cross_compile():
+ self.add_setup_internal_invocation(
+ config.shiboken_generator_option_name,
+ reuse_build=True)
- self.add_setup_internal_invocation(config.pyside_option_name)
+ self.add_setup_internal_invocation(config.pyside_option_name,
+ extra_args=extra_args)
elif config.is_top_level_build_shiboken_module():
self.add_setup_internal_invocation(config.shiboken_module_option_name)
@@ -148,15 +254,21 @@ class SetupRunner(object):
for cmd in self.invocations_list:
cmd_as_string = " ".join(cmd)
- print("\nRunning process: {}\n".format(cmd_as_string))
exit_code = run_process(cmd)
if exit_code != 0:
- msg = textwrap.dedent("""
- setup.py invocation failed with exit code: {}.\n\n
- setup.py invocation was: {}
- """).format(exit_code, cmd_as_string)
+ msg = textwrap.dedent(f"""
+ setup.py invocation failed with exit code: {exit_code}.\n\n
+ setup.py invocation was: {cmd_as_string}
+ """)
raise RuntimeError(msg)
+ if help_requested:
+ print(ADDITIONAL_OPTIONS)
+
+ # Cleanup temp query file.
+ if config.shiboken_host_query_path:
+ os.remove(config.shiboken_host_query_path)
+
@staticmethod
def run_setuptools_setup():
"""
diff --git a/build_scripts/utils.py b/build_scripts/utils.py
index d1bc780dc..74d9e6fc5 100644
--- a/build_scripts/utils.py
+++ b/build_scripts/utils.py
@@ -1,61 +1,25 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
-import sys
+import errno
+import fnmatch
+import glob
import os
import re
-import stat
-import errno
import shutil
+import stat
import subprocess
-import fnmatch
-import itertools
-import glob
+import sys
+import tempfile
+import urllib.request as urllib
+from collections import defaultdict
+from pathlib import Path
+from textwrap import dedent, indent
-# There is no urllib.request in Python2
-try:
- import urllib.request as urllib
-except ImportError:
- import urllib
+from .log import log
+from . import (PYSIDE_PYTHON_TOOLS, PYSIDE_LINUX_BIN_TOOLS, PYSIDE_UNIX_LIBEXEC_TOOLS,
+ PYSIDE_WINDOWS_BIN_TOOLS, PYSIDE_UNIX_BIN_TOOLS, PYSIDE_UNIX_BUNDLED_TOOLS)
-import distutils.log as log
-from distutils.errors import DistutilsSetupError
try:
WindowsError
@@ -63,6 +27,27 @@ except NameError:
WindowsError = None
+def which(name):
+ """
+ Like shutil.which, but accepts a string or a PathLike and returns a Path
+ """
+ path = None
+ try:
+ if isinstance(name, Path):
+ name = str(name)
+ path = shutil.which(name)
+ if path is None:
+ raise TypeError("None was returned")
+ path = Path(path)
+ except TypeError as e:
+ log.error(f"{name} was not found in PATH: {e}")
+ return path
+
+
+def is_64bit():
+ return sys.maxsize > 2147483647
+
+
def filter_match(name, patterns):
for pattern in patterns:
if pattern is None:
@@ -75,281 +60,172 @@ def filter_match(name, patterns):
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))
+ if str(path).lower() not in paths:
+ log.info(f"Inserting path '{path}' to environment")
paths.insert(0, path)
- os.environ['PATH'] = "{}{}{}".format(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("{}\\{}".format(WINSDK_BASE, sdk_version),
- "installationfolder")
- # productversion = Reg.get_value("{}\\{}".format(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 sdk_version not 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
- vsbase = VS_BASE % version
- try:
- productdir = Reg.get_value(r"{}\Setup\VC".format(vsbase), "productdir")
- except KeyError:
- productdir = None
+ os.environ['PATH'] = f"{path}{os.pathsep}{os.environ['PATH']}"
- # 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.0f}0COMNTOOLS".format(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 get_numpy_location():
+ for p in sys.path:
+ if 'site-' in p:
+ numpy = Path(p).resolve() / 'numpy'
+ if numpy.is_dir():
+ return os.fspath(numpy / 'core' / 'include')
+ return None
-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.".format(MSVC_VERSION))
- else:
- log.info("Found {}".format(vcdir_path))
+def platform_cmake_options(as_tuple_list=False):
+ result = []
+ if sys.platform == 'win32':
+ # Prevent cmake from auto-detecting clang if it is in path.
+ if as_tuple_list:
+ result.append(("CMAKE_C_COMPILER", "cl.exe"))
+ result.append(("CMAKE_CXX_COMPILER", "cl.exe"))
+ else:
+ result.append("-DCMAKE_C_COMPILER=cl.exe")
+ result.append("-DCMAKE_CXX_COMPILER=cl.exe")
+ return result
- 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,
- make_writable_by_owner=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))
+def copyfile(src, dst, force=True, _vars=None, force_copy_symlink=False,
+ make_writable_by_owner=False):
+ if isinstance(src, str):
+ src = Path(src.format(**_vars)) if _vars else Path(src)
+ if isinstance(dst, str):
+ dst = Path(dst.format(**_vars)) if _vars else Path(dst)
+ assert (isinstance(src, Path))
+ assert (isinstance(dst, Path))
+
+ if not src.exists() and not force:
+ log.info(f"**Skipping copy file\n {src} to\n {dst}\n Source does not exist")
return
- if not os.path.islink(src) or force_copy_symlink:
- log.info("Copying file {} to {}.".format(src, dst))
+ if not src.is_symlink() or force_copy_symlink:
+ if dst.is_file():
+ src_stat = os.stat(src)
+ dst_stat = os.stat(dst)
+ if (src_stat.st_size == dst_stat.st_size
+ and src_stat.st_mtime <= dst_stat.st_mtime):
+ log.info(f"{dst} is up to date.")
+ return dst
+
+ log.debug(f"Copying file\n {src} to\n {dst}.")
shutil.copy2(src, dst)
if make_writable_by_owner:
make_file_writable_by_owner(dst)
+ return dst
+
+ # We use 'strict=False' to mimic os.path.realpath in case
+ # the directory doesn't exist.
+ link_target_path = src.resolve(strict=False)
+ if link_target_path.parent == src.parent:
+ link_target = Path(link_target_path.name)
+ link_name = Path(src.name)
+ current_directory = Path.cwd()
+ try:
+ target_dir = dst if dst.is_dir() else dst.parent
+ os.chdir(target_dir)
+ if link_name.exists():
+ if (link_name.is_symlink()
+ and os.readlink(link_name) == link_target):
+ log.info(f"Symlink already exists\n {link_name} ->\n {link_target}")
+ return dst
+ os.remove(link_name)
+ log.info(f"Symlinking\n {link_name} ->\n {link_target} in\n {target_dir}")
+ os.symlink(link_target, link_name)
+ except OSError:
+ log.error(f"Error creating symlink\n {link_name} ->\n {link_target}")
+ finally:
+ os.chdir(current_directory)
else:
- link_target_path = os.path.realpath(src)
- if os.path.dirname(link_target_path) == os.path.dirname(src):
- link_target = os.path.basename(link_target_path)
- link_name = os.path.basename(src)
- current_directory = os.getcwd()
- try:
- target_dir = dst if os.path.isdir(dst) else os.path.dirname(dst)
- os.chdir(target_dir)
- if os.path.exists(link_name):
- os.remove(link_name)
- log.info("Symlinking {} -> {} in {}.".format(link_name, link_target, target_dir))
- os.symlink(link_target, link_name)
- except OSError:
- log.error("{} -> {}: Error creating symlink".format(link_name, link_target))
- finally:
- os.chdir(current_directory)
- else:
- log.error("{} -> {}: Can only create symlinks within the same "
- "directory".format(src, link_target_path))
+ log.error(f"{src} -> {link_target_path}: Can only create symlinks within the same "
+ "directory")
return dst
-def makefile(dst, content=None, vars=None):
- if vars is not None:
+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)
+ content = content.format(**_vars)
+ dst = Path(dst.format(**_vars))
- log.info("Making file {}.".format(dst))
+ log.info(f"Making file {dst}.")
- dstdir = os.path.dirname(dst)
- if not os.path.exists(dstdir):
- os.makedirs(dstdir)
+ dstdir = dst.parent
+ if not dstdir.exists():
+ dstdir.mkdir(parents=True)
with open(dst, "wt") as f:
if content is not None:
f.write(content)
-def copydir(src, dst, filter=None, ignore=None, force=True, recursive=True, vars=None,
+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 isinstance(src, str):
+ src = Path(src.format(**_vars)) if _vars else Path(src)
+ if isinstance(dst, str):
+ dst = Path(dst.format(**_vars)) if _vars else Path(dst)
+ assert (isinstance(src, Path))
+ assert (isinstance(dst, Path))
+
+ if _vars is not None:
+ if _filter is not None:
+ _filter = [i.format(**_vars) for i in _filter]
if ignore is not None:
- for i in range(len(ignore)):
- ignore[i] = ignore[i].format(**vars)
+ ignore = [i.format(**_vars) for i in ignore]
- 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))
+ if not src.exists() and not force:
+ log.info(f"**Skipping copy tree\n {src} to\n {dst}\n Source does not exist. "
+ f"filter={_filter}. ignore={ignore}.")
return []
- log.info("Copying tree {} to {}. filter={}. ignore={}.".format(src, dst, filter, ignore))
+ log.debug(f"Copying tree\n {src} to\n {dst}. filter={_filter}. ignore={ignore}.")
names = os.listdir(src)
results = []
- errors = []
+ copy_errors = []
for name in names:
- srcname = os.path.join(src, name)
- dstname = os.path.join(dst, name)
+ srcname = src / name
+ dstname = dst / name
try:
- if os.path.isdir(srcname):
+ if srcname.is_dir():
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,
+ 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 (_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))
+ if not dst.is_dir():
+ dst.mkdir(parents=True)
+ 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])
+ copy_errors.extend(err.args[0])
except EnvironmentError as why:
- errors.append((srcname, dstname, str(why)))
+ copy_errors.append((srcname, dstname, str(why)))
try:
- if os.path.exists(dst):
- shutil.copystat(src, dst)
+ if dst.exists():
+ shutil.copystat(str(src), str(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)
+ copy_errors.extend((src, dst, str(why)))
+ if copy_errors:
+ raise EnvironmentError(copy_errors)
return results
@@ -358,9 +234,10 @@ def make_file_writable_by_owner(path):
os.chmod(path, current_permissions | stat.S_IWUSR)
-def rmtree(dirname, ignore=False):
+def remove_tree(dirname, ignore=False):
def handle_remove_readonly(func, path, exc):
- excvalue = exc[1]
+ # exc returns like 'sys.exc_info()': type, value, traceback
+ _, excvalue, _ = exc
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)
@@ -372,13 +249,12 @@ def rmtree(dirname, ignore=False):
def run_process_output(args, initial_env=None):
if initial_env is None:
initial_env = os.environ
- std_out = subprocess.Popen(args, env=initial_env, universal_newlines=1,
- stdout=subprocess.PIPE).stdout
result = []
- for raw_line in std_out.readlines():
- line = raw_line if sys.version_info >= (3,) else raw_line.decode('utf-8')
- result.append(line.rstrip())
- std_out.close()
+ with subprocess.Popen(args, env=initial_env, universal_newlines=1,
+ stdout=subprocess.PIPE) as p:
+ for raw_line in p.stdout.readlines():
+ result.append(raw_line.rstrip())
+ p.stdout.close()
return result
@@ -387,8 +263,8 @@ def run_process(args, initial_env=None):
Run process until completion and return the process exit code.
No output is captured.
"""
- command = " ".join([(" " in x and '"{}"'.format(x) or x) for x in args])
- log.info("Running process in directory {}: command {}".format(os.getcwd(), command))
+ command = " ".join([(" " in x and f'"{x}"' or x) for x in args])
+ log.debug(f"In directory {Path.cwd()}:\n\tRunning command: {command}")
if initial_env is None:
initial_env = os.environ
@@ -400,83 +276,10 @@ def run_process(args, initial_env=None):
return exit_code
-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
- lines = map(lambda s: s.decode(), 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
- # parse key/values into pairs
- pairs = map(lambda l: l.rstrip().split('=', 1), 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
+ Run command `cmd`, return stdout, or (stdout, stderr,
+ return_code) if `ret_err` is True.
Parameters
----------
@@ -500,23 +303,20 @@ def back_tick(cmd, ret_err=False):
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("{} process did not terminate".format(cmd))
- if retcode != 0 and not ret_err:
- raise RuntimeError("{} process returned code {}\n*** {}".format(
- (cmd, retcode, err)))
- out = out.strip()
+ with subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, shell=True) as proc:
+ out_bytes, err_bytes = proc.communicate()
+ out = out_bytes.decode().strip()
+ err = err_bytes.decode().strip()
+ retcode = proc.returncode
+ if retcode is None and not ret_err:
+ proc.terminate()
+ raise RuntimeError(f"{cmd} process did not terminate")
+ if retcode != 0 and not ret_err:
+ raise RuntimeError(f"{cmd} process returned code {retcode}\n*** {err}")
if not ret_err:
return out
- return out, err.strip(), retcode
+ return out, err, retcode
MACOS_OUTNAME_RE = re.compile(r'\(compatibility version [\d.]+, current version [\d.]+\)')
@@ -536,7 +336,7 @@ def macos_get_install_names(libpath):
install_names : list of str
install names in library `libpath`
"""
- out = back_tick("otool -L {}".format(libpath))
+ out = back_tick(f"otool -L {libpath}")
libs = [line for line in out.split('\n')][1:]
return [MACOS_OUTNAME_RE.sub('', lib).strip() for lib in libs]
@@ -561,7 +361,7 @@ def macos_get_rpaths(libpath):
-----
See ``man dyld`` for more information on rpaths in libraries
"""
- lines = back_tick('otool -l {}'.format(libpath)).split('\n')
+ lines = back_tick(f"otool -l {libpath}").split('\n')
ctr = 0
rpaths = []
while ctr < len(lines):
@@ -573,14 +373,17 @@ def macos_get_rpaths(libpath):
rpath_line = lines[ctr + 2].strip()
match = MACOS_RPATH_RE.match(rpath_line)
if match is None:
- raise RuntimeError("Unexpected path line: {}".format(rpath_line))
+ raise RuntimeError(f"Unexpected path line: {rpath_line}")
rpaths.append(match.groups()[0])
ctr += 3
return rpaths
def macos_add_rpath(rpath, library_path):
- back_tick("install_name_tool -add_rpath {} {}".format(rpath, library_path))
+ try:
+ back_tick(f"install_name_tool -add_rpath {rpath} {library_path}")
+ except RuntimeError as e:
+ print(f"Exception {type(e).__name__}: {e}")
def macos_fix_rpaths_for_library(library_path, qt_lib_dir):
@@ -627,8 +430,8 @@ def macos_fix_rpaths_for_library(library_path, qt_lib_dir):
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=[]):
+def macos_add_qt_rpath(library_path, qt_lib_dir, existing_rpath_commands=None,
+ library_dependencies=None):
"""
Adds an rpath load command to the Qt lib directory if necessary
@@ -636,6 +439,12 @@ def macos_add_qt_rpath(library_path, qt_lib_dir, existing_rpath_commands=[],
and adds an rpath load command that points to the Qt lib directory
(qt_lib_dir).
"""
+ if existing_rpath_commands is None:
+ existing_rpath_commands = []
+
+ if library_dependencies is None:
+ library_dependencies = []
+
if not existing_rpath_commands:
existing_rpath_commands = macos_get_rpaths(library_path)
@@ -664,31 +473,19 @@ def find_glob_in_path(pattern):
pattern += '.exe'
for path in os.environ.get('PATH', '').split(os.pathsep):
- for match in glob.glob(os.path.join(path, pattern)):
+ for match in glob.glob(str(Path(path) / pattern)):
result.append(match)
return result
-# Locate the most recent version of llvm_config in the path.
-def find_llvm_config():
- version_re = re.compile(r'(\d+)\.(\d+)\.(\d+)')
- result = None
- last_version_string = '000000'
- for llvm_config in find_glob_in_path('llvm-config*'):
- try:
- output = run_process_output([llvm_config, '--version'])
- if output:
- match = version_re.match(output[0])
- if match:
- version_string = "{:02d}{:02d}{:02d}".format(int(match.group(1)),
- int(match.group(2)),
- int(match.group(3)))
- if (version_string > last_version_string):
- result = llvm_config
- last_version_string = version_string
- except OSError:
- pass
- return result
+# Expand the __ARCH_ place holder in the CLANG environment variables
+def expand_clang_variables(target_arch):
+ for var in 'LLVM_INSTALL_DIR', 'CLANG_INSTALL_DIR':
+ value = os.environ.get(var)
+ if value and '_ARCH_' in value:
+ value = value.replace('_ARCH_', target_arch)
+ os.environ[var] = value
+ print(f"{var} = {value}")
# Add Clang to path for Windows for the shiboken ApiExtractor tests.
@@ -700,18 +497,8 @@ def detect_clang():
source = 'CLANG_INSTALL_DIR'
clang_dir = os.environ.get(source, None)
if not clang_dir:
- source = find_llvm_config()
- try:
- if source is not None:
- output = run_process_output([source, '--prefix'])
- if output:
- clang_dir = output[0]
- except OSError:
- pass
- if clang_dir:
- arch = '64' if sys.maxsize > 2 ** 31 - 1 else '32'
- clang_dir = clang_dir.replace('_ARCH_', arch)
- return (clang_dir, source)
+ raise OSError("clang not found")
+ return (Path(clang_dir), source)
_7z_binary = None
@@ -723,29 +510,29 @@ def download_and_extract_7z(fileurl, target):
localfile = None
for i in range(1, 10):
try:
- print("Downloading fileUrl {}, attempt #{}".format(fileurl, i))
+ log.info(f"Downloading fileUrl {fileurl}, attempt #{i}")
localfile, info = urllib.urlretrieve(fileurl)
break
- except:
+ except urllib.URLError:
pass
if not localfile:
- print("Error downloading {} : {}".format(fileurl, info))
- raise RuntimeError(' Error downloading {}'.format(fileurl))
+ log.error(f"Error downloading {fileurl} : {info}")
+ raise RuntimeError(f" Error downloading {fileurl}")
try:
global _7z_binary
- outputDir = "-o{}".format(target)
+ outputDir = f"-o{target}"
if not _7z_binary:
- if sys.platform == 'win32':
- candidate = 'c:\\Program Files\\7-Zip\\7z.exe'
- if os.path.exists(candidate):
+ if sys.platform == "win32":
+ candidate = Path("c:\\Program Files\\7-Zip\\7z.exe")
+ if candidate.exists():
_7z_binary = candidate
if not _7z_binary:
_7z_binary = '7z'
- print("calling {} x {} {}".format(_7z_binary, localfile, outputDir))
+ log.info(f"calling {_7z_binary} x {localfile} {outputDir}")
subprocess.call([_7z_binary, "x", "-y", localfile, outputDir])
- except:
- raise RuntimeError(' Error extracting {}'.format(localfile))
+ except (subprocess.CalledProcessError, OSError):
+ raise RuntimeError(f"Error extracting {localfile}")
def split_and_strip(sinput):
@@ -803,18 +590,49 @@ def ldd_get_paths_for_dependencies(dependencies_regex, executable_path=None, dep
return paths
-def ldd(executable_path):
+def _ldd_ldd(executable_path):
+ """Helper for ldd():
+ Returns ldd output of shared library dependencies for given
+ `executable_path`.
+
+ Parameters
+ ----------
+ executable_path : str
+ path to executable or shared library.
+
+ Returns
+ -------
+ output : str
+ the raw output retrieved from the dynamic linker.
"""
+
+ output = ''
+ error = ''
+ try:
+ output_lines = run_process_output(['ldd', executable_path])
+ output = '\n'.join(output_lines)
+ except Exception as e:
+ error = str(e)
+ if not output:
+ message = (f"ldd failed to query for dependent shared libraries of {executable_path}: "
+ f"{error}")
+ raise RuntimeError(message)
+ return output
+
+
+def _ldd_ldso(executable_path):
+ """
+ Helper for ldd():
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.
+ This is a partial port of /usr/bin/ldd from bash to Python for
+ systems that do not have ldd.
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.
+ Only works on Linux.
This is because ldd (on Ubuntu) is shipped in the libc-bin package
that, which might have a
minuscule percentage of not being installed.
@@ -837,12 +655,13 @@ def ldd(executable_path):
# Choose appropriate runtime dynamic linker.
for rtld in rtld_list:
- if os.path.isfile(rtld) and os.access(rtld, os.X_OK):
+ rtld = Path(rtld)
+ if rtld.is_file() 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)
+ (_, _, code) = back_tick(f"{rtld} --verify {executable_path}", True)
# Codes 0 and 2 mean given executable_path can be
# understood by ld.so.
if code in [0, 2]:
@@ -854,25 +673,51 @@ def ldd(executable_path):
# Query for shared library dependencies.
rtld_env = "LD_TRACE_LOADED_OBJECTS=1"
- rtld_cmd = "{} {} {}".format(rtld_env, chosen_rtld, executable_path)
+ rtld_cmd = f"{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))
+ f"libraries of {executable_path}")
+
+
+def ldd(executable_path):
+ """
+ Returns ldd output of shared library dependencies for given `executable_path`,
+ using either ldd or ld.so depending on availability.
+
+ Parameters
+ ----------
+ executable_path : str
+ path to executable or shared library.
+
+ Returns
+ -------
+ output : str
+ the raw output retrieved from the dynamic linker.
+ """
+ result = ''
+ try:
+ result = _ldd_ldd(executable_path)
+ except RuntimeError as e:
+ message = f"ldd: Falling back to ld.so ({str(e)})"
+ log.warning(message)
+ if not result:
+ result = _ldd_ldso(executable_path)
+ return result
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)
+ final_pattern = Path(path) / pattern
+ maybe_files = glob.glob(str(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.?")
+ maybe_file = find_files_using_glob(lib_dir, "libQt6Core.so.?")
if len(maybe_file) == 1:
return maybe_file[0]
return None
@@ -883,16 +728,18 @@ def find_qt_core_library_glob(lib_dir):
# ldd for the specified platforms.
# This has less priority because ICU libs are not used in the default
# Qt configuration build.
+# Note: Uses ldd to query shared library dependencies and thus does not
+# work for cross builds.
def copy_icu_libs(patchelf, 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)
+ qt_core_library_path = 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))
+ if not qt_core_library_path or not qt_core_library_path.exists():
+ raise RuntimeError(f"QtCore library does not exist at path: {qt_core_library_path}. "
+ "Failed to copy ICU libraries.")
dependencies = ldd_get_dependencies(qt_core_library_path)
@@ -909,14 +756,15 @@ def copy_icu_libs(patchelf, destination_lib_dir):
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.')
+ log.debug('Copying the detected ICU libraries required by QtCore.')
- if not os.path.exists(destination_lib_dir):
- os.makedirs(destination_lib_dir)
+ destination_lib_dir = Path(destination_lib_dir)
+ if not destination_lib_dir.exists():
+ destination_lib_dir.mkdir(parents=True)
for path in paths:
- basename = os.path.basename(path)
- destination = os.path.join(destination_lib_dir, basename)
+ basename = Path(path).name
+ destination = 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.
@@ -925,20 +773,15 @@ def copy_icu_libs(patchelf, destination_lib_dir):
# 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 = linux_get_rpaths(qt_core_library_path)
- if not rpaths or not rpaths_has_origin(rpaths):
- log.info('Patching QtCore library to contain $ORIGIN rpath.')
- rpaths.insert(0, '$ORIGIN')
- new_rpaths_string = ":".join(rpaths)
- linux_set_rpaths(patchelf, qt_core_library_path, new_rpaths_string)
+ linux_prepend_rpath(patchelf, qt_core_library_path, '$ORIGIN')
def linux_run_read_elf(executable_path):
- cmd = "readelf -d {}".format(executable_path)
+ cmd = f"readelf -d {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))
+ raise RuntimeError(f"Running `readelf -d {executable_path}` failed with error "
+ f"output:\n {err}. ")
lines = split_and_strip(out)
return lines
@@ -946,10 +789,24 @@ def linux_run_read_elf(executable_path):
def linux_set_rpaths(patchelf, executable_path, rpath_string):
""" Patches the `executable_path` with a new rpath string. """
- cmd = [patchelf, '--set-rpath', rpath_string, executable_path]
+ cmd = [str(patchelf), '--set-rpath', str(rpath_string), str(executable_path)]
if run_process(cmd) != 0:
- raise RuntimeError("Error patching rpath in {}".format(executable_path))
+ raise RuntimeError(f"Error patching rpath in {executable_path}")
+
+
+def linux_prepend_rpath(patchelf, executable_path, new_path):
+ """ Prepends a path to the rpaths of the executable unless it has ORIGIN. """
+ rpaths = linux_get_rpaths(executable_path)
+ if not rpaths or not rpaths_has_origin(rpaths):
+ rpaths.insert(0, new_path)
+ new_rpaths_string = ":".join(rpaths)
+ linux_set_rpaths(patchelf, executable_path, new_rpaths_string)
+
+
+def linux_patch_executable(patchelf, executable_path):
+ """ Patch an executable to run with the Qt libraries. """
+ linux_prepend_rpath(patchelf, executable_path, '$ORIGIN/../lib')
def linux_get_dependent_libraries(executable_path):
@@ -1036,6 +893,7 @@ def linux_fix_rpaths_for_library(patchelf, executable_path, qt_rpath, override=F
existing_rpaths = linux_get_rpaths(executable_path)
rpaths.extend(existing_rpaths)
+ qt_rpath = str(qt_rpath)
if linux_needs_qt_rpath(executable_path) and qt_rpath not in existing_rpaths:
rpaths.append(qt_rpath)
@@ -1069,86 +927,237 @@ def get_python_dict(python_script_path):
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))
+ print(f"get_python_dict: Couldn't get dict from python "
+ f"file: {python_script_path}. {e}")
raise
-def install_pip_package_from_url_specifier(env_pip, url, upgrade=True):
- args = [env_pip, "install", url]
- if upgrade:
- args.append("--upgrade")
- args.append(url)
- run_instruction(args, "Failed to install {}".format(url))
-
-
-def install_pip_dependencies(env_pip, packages, upgrade=True):
- for p in packages:
- args = [env_pip, "install"]
- if upgrade:
- args.append("--upgrade")
- args.append(p)
- run_instruction(args, "Failed to install {}".format(p))
-
-
def get_qtci_virtualEnv(python_ver, host, hostArch, targetArch):
_pExe = "python"
- _env = "env{}".format(str(python_ver))
- env_python = _env + "/bin/python"
- env_pip = _env + "/bin/pip"
+ _env = f"{os.environ.get('PYSIDE_VIRTUALENV') or 'env'+python_ver}"
+ env_python = f"{_env}/bin/python"
+ env_pip = f"{_env}/bin/pip"
if host == "Windows":
- print("New virtualenv to build {} in {} host".format(targetArch, hostArch))
+ log.info("New virtualenv to build {targetArch} in {hostArch} host")
_pExe = "python.exe"
# With windows we are creating building 32-bit target in 64-bit host
if hostArch == "X86_64" and targetArch == "X86":
- if python_ver == "3":
- _pExe = os.path.join(os.getenv("PYTHON3_32_PATH"), "python.exe")
+ if python_ver.startswith("3"):
+ var = f"PYTHON{python_ver}-32_PATH"
+ log.info(f"Try to find python from {var} env variable")
+ _path = Path(os.getenv(var, ""))
+ _pExe = _path / "python.exe"
+ if not _pExe.is_file():
+ log.warning(f"Can't find python.exe from {_pExe}, using default python3")
+ _pExe = Path(os.getenv("PYTHON3_32_PATH")) / "python.exe"
else:
- _pExe = os.path.join(os.getenv("PYTHON2_32_PATH"), "python.exe")
+ _pExe = Path(os.getenv("PYTHON2_32_PATH")) / "python.exe"
else:
- if python_ver == "3":
- _pExe = os.path.join(os.getenv("PYTHON3_PATH"), "python.exe")
- env_python = _env + "\\Scripts\\python.exe"
- env_pip = _env + "\\Scripts\\pip.exe"
+ if python_ver.startswith("3"):
+ var = f"PYTHON{python_ver}-64_PATH"
+ log.info(f"Try to find python from {var} env variable")
+ _path = Path(os.getenv(var, ""))
+ _pExe = _path / "python.exe"
+ if not _pExe.is_file():
+ log.warning(f"Can't find python.exe from {_pExe}, using default python3")
+ _pExe = Path(os.getenv("PYTHON3_PATH")) / "python.exe"
+ env_python = f"{_env}\\Scripts\\python.exe"
+ env_pip = f"{_env}\\Scripts\\pip.exe"
else:
- if python_ver == "3":
+ _pExe = f"python{python_ver}"
+ try:
+ run_instruction([_pExe, "--version"], f"Failed to guess python version {_pExe}")
+ except Exception as e:
+ print(f"Exception {type(e).__name__}: {e}")
_pExe = "python3"
- return(_pExe, _env, env_pip, env_python)
+ return (_pExe, _env, env_pip, env_python)
def run_instruction(instruction, error, initial_env=None):
if initial_env is None:
initial_env = os.environ
- print("Running Coin instruction: {}".format(' '.join(str(e) for e in instruction)))
+ log.info(f"Running Coin instruction: {' '.join(str(e) for e in instruction)}")
result = subprocess.call(instruction, env=initial_env)
if result != 0:
- print("ERROR : {}".format(error))
+ log.error(f"ERROR : {error}")
exit(result)
-def acceptCITestConfiguration(hostOS, hostOSVer, targetArch, compiler):
- # Disable unsupported CI configs for now
- # NOTE: String must match with QT CI's storagestruct thrift
- if (hostOSVer in ["WinRT_10", "WebAssembly", "Ubuntu_18_04", "Android_ANY"]
- or hostOSVer.startswith("SLES_")):
- print("Disabled {} from Coin configuration".format(hostOSVer))
- return False
- # With 5.11 CI will create two sets of release binaries,
- # one with msvc 2015 and one with msvc 2017
- # we shouldn't release the 2015 version.
- # BUT, 32 bit build is done only on msvc 2015...
- if compiler in ["MSVC2015"] and targetArch in ["X86_64"]:
- print("Disabled {} to {} from Coin configuration".format(compiler, targetArch))
- return False
- return True
+def get_ci_qtpaths_path(ci_install_dir, ci_host_os):
+ qtpaths_path = f"--qtpaths={ci_install_dir}"
+ if ci_host_os == "MacOS":
+ return f"{qtpaths_path}/bin/qtpaths"
+ elif ci_host_os == "Windows":
+ return f"{qtpaths_path}\\bin\\qtpaths.exe"
+ else:
+ return f"{qtpaths_path}/bin/qtpaths"
def get_ci_qmake_path(ci_install_dir, ci_host_os):
- qmake_path = "--qmake={}".format(ci_install_dir)
+ qmake_path = f"--qmake={ci_install_dir}"
if ci_host_os == "MacOS":
- return qmake_path + "/bin/qmake"
+ return f"{qmake_path}/bin/qmake"
elif ci_host_os == "Windows":
- return qmake_path + "\\bin\\qmake.exe"
+ return f"{qmake_path}\\bin\\qmake.exe"
else:
- return qmake_path + "/bin/qmake"
+ return f"{qmake_path}/bin/qmake"
+
+
+def parse_cmake_conf_assignments_by_key(source_dir):
+ """
+ Parses a .cmake.conf file that contains set(foo "bar") assignments
+ and returns a dict with those assignments transformed to keys and
+ values.
+ """
+
+ contents = (Path(source_dir) / ".cmake.conf").read_text()
+ matches = re.findall(r'set\((.+?) "(.*?)"\)', contents)
+ d = {key: value for key, value in matches}
+ return d
+
+
+def _configure_failure_message(project_path, cmd, return_code, output, error, env):
+ """Format a verbose message about configure_cmake_project() failures."""
+ cmd_string = ' '.join(cmd)
+ error_text = indent(error.strip(), " ")
+ output_text = indent(output.strip(), " ")
+ result = dedent(f"""
+ Failed to configure CMake project: '{project_path}'
+ Configure args were:
+ {cmd_string}
+ Return code: {return_code}
+ """)
+
+ first = True
+ for k, v in env.items():
+ if k.startswith("CMAKE"):
+ if first:
+ result += "Environment:\n"
+ first = False
+ result += f" {k}={v}\n"
+
+ result += f"\nwith error:\n{error_text}\n"
+
+ CMAKE_CMAKEOUTPUT_LOG_PATTERN = r'See also "([^"]+CMakeOutput\.log)"\.'
+ cmakeoutput_log_match = re.search(CMAKE_CMAKEOUTPUT_LOG_PATTERN, output)
+ if cmakeoutput_log_match:
+ cmakeoutput_log = Path(cmakeoutput_log_match.group(1))
+ if cmakeoutput_log.is_file():
+ log = indent(cmakeoutput_log.read_text().strip(), " ")
+ result += f"CMakeOutput.log:\n{log}\n"
+
+ result += f"Output:\n{output_text}\n"
+ return result
+
+
+def configure_cmake_project(project_path,
+ cmake_path,
+ build_path=None,
+ temp_prefix_build_path=None,
+ cmake_args=None,
+ cmake_cache_args=None,
+ ):
+ clean_temp_dir = False
+ if not build_path:
+ # Ensure parent dir exists.
+ if temp_prefix_build_path:
+ os.makedirs(temp_prefix_build_path, exist_ok=True)
+
+ project_name = Path(project_path).name
+ build_path = tempfile.mkdtemp(prefix=f"{project_name}_", dir=temp_prefix_build_path)
+
+ if 'QFP_SETUP_KEEP_TEMP_FILES' not in os.environ:
+ clean_temp_dir = True
+
+ cmd = [cmake_path, '-G', 'Ninja', '-S', project_path, '-B', build_path]
+
+ if cmake_args:
+ cmd.extend(cmake_args)
+
+ for arg, value in cmake_cache_args:
+ cmd.extend([f'-D{arg}={value}'])
+
+ cmd = [str(i) for i in cmd]
+
+ proc = subprocess.run(cmd, shell=False, cwd=build_path,
+ capture_output=True, universal_newlines=True)
+ return_code = proc.returncode
+ output = proc.stdout
+ error = proc.stderr
+
+ if return_code != 0:
+ m = _configure_failure_message(project_path, cmd, return_code,
+ output, error, os.environ)
+ raise RuntimeError(m)
+
+ if clean_temp_dir:
+ remove_tree(build_path)
+
+ return output
+
+
+def parse_cmake_project_message_info(output):
+ # Parse the output for anything prefixed
+ # '-- qfp:<category>:<key>: <value>' as created by the message()
+ # calls in a given CMake project and store it in a python dict.
+ result = defaultdict(lambda: defaultdict(str))
+ pattern = re.compile(r"^-- qfp:(.+?):(.+?):(.*)$")
+ for line in output.splitlines():
+ found = pattern.search(line)
+ if found:
+ category = found.group(1).strip()
+ key = found.group(2).strip()
+ value = found.group(3).strip()
+ result[category][key] = str(value)
+ return result
+
+
+def available_pyside_tools(qt_tools_path: Path, package_for_wheels: bool = False):
+ pyside_tools = PYSIDE_PYTHON_TOOLS.copy()
+
+ if package_for_wheels:
+ # Qt wrappers in build/{python_env_name}/package_for_wheels/PySide6
+ bin_path = qt_tools_path
+ else:
+ bin_path = qt_tools_path / "bin"
+
+ def tool_exist(tool_path: Path):
+ if tool_path.exists():
+ return True
+ else:
+ log.warning(f"{tool_path} not found. pyside-{tool_path.name} not included.")
+ return False
+
+ if sys.platform == 'win32':
+ pyside_tools.extend([tool for tool in PYSIDE_WINDOWS_BIN_TOOLS
+ if tool_exist(bin_path / f"{tool}.exe")])
+ else:
+ lib_exec_path = qt_tools_path / "Qt" / "libexec" if package_for_wheels \
+ else qt_tools_path / "libexec"
+ pyside_tools.extend([tool for tool in PYSIDE_UNIX_LIBEXEC_TOOLS
+ if tool_exist(lib_exec_path / tool)])
+ if sys.platform == 'darwin':
+ def name_to_path(name):
+ return f"{name.capitalize()}.app/Contents/MacOS/{name.capitalize()}"
+
+ pyside_tools.extend([tool for tool in PYSIDE_UNIX_BIN_TOOLS
+ if tool_exist(bin_path / tool)])
+ pyside_tools.extend([tool for tool in PYSIDE_UNIX_BUNDLED_TOOLS
+ if tool_exist(bin_path / name_to_path(tool))])
+ else:
+ pyside_tools.extend([tool for tool in PYSIDE_LINUX_BIN_TOOLS
+ if tool_exist(bin_path / tool)])
+
+ return pyside_tools
+
+
+def copy_qt_metatypes(destination_qt_dir, _vars):
+ """Copy the Qt metatypes files which changed location in 6.5"""
+ # <qt>/[lib]?/metatypes/* -> <setup>/{st_package_name}/Qt/[lib]?/metatypes
+ qt_meta_types_dir = "{qt_metatypes_dir}".format(**_vars)
+ qt_prefix_dir = "{qt_prefix_dir}".format(**_vars)
+ rel_meta_data_dir = os.fspath(Path(qt_meta_types_dir).relative_to(qt_prefix_dir))
+ copydir(qt_meta_types_dir, destination_qt_dir / rel_meta_data_dir,
+ _filter=["*.json"],
+ recursive=False, _vars=_vars, force_copy_symlinks=True)
diff --git a/build_scripts/wheel_files.py b/build_scripts/wheel_files.py
new file mode 100644
index 000000000..d34ada113
--- /dev/null
+++ b/build_scripts/wheel_files.py
@@ -0,0 +1,1036 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+
+import json
+import sys
+from dataclasses import Field, dataclass, field
+from typing import Dict, List
+
+
+_pyside_package_path = None
+_module_json_file_path = None
+
+
+def set_pyside_package_path(p):
+ global _pyside_package_path, _module_json_file_path
+ _pyside_package_path = p
+ qt_path = p
+ if sys.platform != "win32":
+ qt_path /= "Qt"
+ _module_json_file_path = qt_path / "modules"
+
+
+def get_module_json_data(module):
+ """Read the JSON module data."""
+ json_path = _module_json_file_path / f"{module}.json"
+ json_data = None
+ if not json_path.is_file(): # Wayland is Linux only
+ print(f"Skipping {json_path}", file=sys.stderr)
+ return None
+ with json_path.open(encoding="utf-8") as json_file:
+ json_data = json.load(json_file)
+ return json_data
+
+
+def get_module_plugins(json_data):
+ """Return the plugins from the JSON module data."""
+ if json_data:
+ plugins = json_data.get("plugin_types")
+ if plugins:
+ return plugins
+ return []
+
+
+# This dataclass is in charge of holding the file information
+# that each Qt module needs to have to be packaged in a wheel
+@dataclass
+class ModuleData:
+ name: str
+ ext: str = ""
+ # Libraries not related to Qt modules
+ lib: List[str] = field(default_factory=list)
+ # Libraries related to Qt modules
+ qtlib: List[str] = field(default_factory=list)
+ # Files from the Qt/qml directory
+ qml: List[str] = field(default_factory=list)
+ pyi: List[str] = field(default_factory=list)
+ translations: List[str] = field(default_factory=list)
+ typesystems: List[str] = field(default_factory=list)
+ include: List[str] = field(default_factory=list)
+ glue: List[str] = field(default_factory=list)
+ metatypes: List[str] = field(default_factory=list)
+ plugins: List[str] = field(default_factory=list)
+
+ # For special cases when a file/directory doesn't fall into
+ # the previous categories.
+ extra_dirs: List[str] = field(default_factory=list)
+ extra_files: List[str] = field(default_factory=list)
+
+ # Once the object is created, this method will be executed
+ # and automatically will initialize some of the files that are
+ # common for each module.
+ # Note: The goal of this list is to be used for a MANIFEST.in
+ # meaning that in case a file gets added and it doesn't
+ # exist, the wheel creation process will only throw a
+ # warning, but it will not interrupt the packaging process.
+ def __post_init__(self) -> None:
+ if not self.ext:
+ self.ext = self.get_extension_from_platform(sys.platform)
+ _lo = self.name.lower()
+
+ self.lib.append(f"Qt{self.name}")
+ self.qtlib.append(f"libQt6{self.name}")
+ if not len(self.qml):
+ self.qml.append(f"Qt{self.name}")
+ self.pyi.append(f"Qt{self.name}.pyi")
+ self.typesystems.append(f"typesystem_{_lo}.xml")
+ self.include.append(f"Qt{self.name}/*.h")
+ self.glue.append(f"qt{_lo}.cpp")
+ if not len(self.metatypes):
+ self.metatypes.append(f"qt6{_lo}_relwithdebinfo_metatypes.json")
+
+ # The PySide6 directory that gets packaged by the build_scripts
+ # 'prepare_packages()' has a certain structure that depends on
+ # the platform. Because that directory is the base for the wheel
+ # packaging to work, we use the relative paths that are included
+ # on each file.
+ # Note: The MANIFEST.in file doesn't need to have '\' or other
+ # separator, and respect the '/' even on Windows.
+ def adjusts_paths_and_extensions(self) -> None:
+ if sys.platform == "win32":
+ self.lib = [f"{i}.*{self.ext}".replace("lib", "") for i in self.lib]
+ self.qtlib = [f"{i}.*dll".replace("lib", "") for i in self.qtlib]
+ self.qml = [f"qml/{i}" for i in self.qml]
+ self.translations = [f"translations/{i}" for i in self.translations]
+ self.metatypes = [
+ f"metatypes/{i}".replace("_relwithdebinfo", "") for i in self.metatypes
+ ]
+ self.plugins = [f"plugins/{i}" for i in self.plugins]
+ else:
+ if sys.platform == "darwin":
+ self.qtlib = [f"Qt/lib/{i.replace('libQt6', 'Qt')}.framework" for i in self.qtlib]
+ self.lib = [self.macos_pyside_wrappers_lib(i) for i in self.lib]
+ else:
+ self.lib = [f"{i}.*{self.ext}*" for i in self.lib]
+ self.qtlib = [f"Qt/lib/{i}.*{self.ext}*" for i in self.qtlib]
+ self.qml = [f"Qt/qml/{i}" for i in self.qml]
+ self.translations = [f"Qt/translations/{i}" for i in self.translations]
+ self.metatypes = [f"Qt/metatypes/{i}" for i in self.metatypes]
+ self.plugins = [f"Qt/plugins/{i}" for i in self.plugins]
+
+ self.typesystems = [f"typesystems/{i}" for i in self.typesystems]
+ self.include = [f"include/{i}" for i in self.include]
+ self.glue = [f"glue/{i}" for i in self.glue]
+
+ def macos_pyside_wrappers_lib(self, s):
+ if s.startswith("Qt"):
+ return f"{s}.*so*"
+ else:
+ return f"{s}.*{self.ext}*"
+
+ @classmethod
+ def get_fields(cls) -> Dict[str, Field]:
+ return cls.__dataclass_fields__
+
+ @staticmethod
+ def get_extension_from_platform(platform: str) -> str:
+ if platform == "linux":
+ return "so"
+ elif platform == "darwin":
+ return "dylib"
+ elif platform == "win32":
+ return "pyd"
+ else:
+ print(f"Platform '{platform}' not supported. Exiting")
+ sys.exit(-1)
+
+
+# Wheels auxiliary functions to return the ModuleData objects
+# for each module that will be included in the wheel.
+
+# PySide wheel
+def wheel_files_pyside_essentials() -> List[ModuleData]:
+ files = [
+ module_QtCore(),
+ module_QtGui(),
+ module_QtWidgets(),
+ module_QtHelp(),
+ module_QtNetwork(),
+ module_QtConcurrent(),
+ module_QtDBus(),
+ module_QtDesigner(),
+ module_QtOpenGL(),
+ module_QtOpenGLWidgets(),
+ module_QtPrintSupport(),
+ module_QtQml(),
+ module_QtQuick(),
+ module_QtQuickControls2(),
+ module_QtQuickTest(),
+ module_QtQuickWidgets(),
+ module_QtXml(),
+ module_QtTest(),
+ module_QtSql(),
+ module_QtSvg(),
+ module_QtSvgWidgets(),
+ module_QtUiTools(),
+ module_QtExampleIcons(),
+ # Only for plugins
+ module_QtWayland(),
+ # there are no bindings for these modules, but their binaries are
+ # required for qmlls
+ module_QtLanguageServer(),
+ module_QtJsonRpc(),
+ ]
+ return files
+
+
+# PySide Addons wheel
+def wheel_files_pyside_addons() -> List[ModuleData]:
+ files = [
+ module_Qt3DAnimation(),
+ module_Qt3DCore(),
+ module_Qt3DExtras(),
+ module_Qt3DInput(),
+ module_Qt3DLogic(),
+ module_Qt3DRender(),
+ module_QtAxContainer(),
+ module_QtBluetooth(),
+ module_QtCharts(),
+ module_QtDataVisualization(),
+ module_QtGraphs(),
+ module_QtMultimedia(),
+ module_QtMultimediaWidgets(),
+ module_QtNetworkAuth(),
+ module_QtNfc(),
+ module_QtPdf(),
+ module_QtPdfWidgets(),
+ module_QtPositioning(),
+ module_QtQuick3D(),
+ module_QtRemoteObjects(),
+ module_QtScxml(),
+ module_QtSensors(),
+ module_QtSerialPort(),
+ module_QtSerialBus(),
+ module_QtSpatialAudio(),
+ module_QtStateMachine(),
+ module_QtTextToSpeech(),
+ module_QtVirtualKeyboard(),
+ module_QtWebChannel(),
+ module_QtWebEngineCore(),
+ module_QtWebEngineQuick(),
+ module_QtWebEngineWidgets(),
+ module_QtWebSockets(),
+ module_QtHttpServer(),
+ module_QtLocation(),
+ module_QtAsyncio(),
+ ]
+ return files
+
+
+# Functions that hold the information of all the files that needs
+# to be included for the module to work, including Qt libraries,
+# typesystems, glue, etc.
+def module_QtCore() -> ModuleData:
+ # QtCore
+ data = ModuleData("Core")
+
+ _typesystems = [
+ "common.xml",
+ "core_common.xml",
+ "typesystem_core_common.xml",
+ "typesystem_core_win.xml"
+ ]
+
+ data.typesystems.extend(_typesystems)
+ data.include.append("*.h")
+ if sys.platform == "win32":
+ data.qtlib.append("pyside6.*")
+ data.extra_files.append("qt.conf")
+ data.extra_files.append("rcc.exe")
+ data.extra_files.append("qtdiag.exe")
+ data.extra_files.append("pyside6.*.lib")
+ data.extra_files.append("resources/icudtl.dat")
+ from build_scripts.platforms.windows_desktop import msvc_redist
+ data.extra_files.extend(msvc_redist)
+ else:
+ data.lib.append("libpyside6.*")
+ data.extra_files.append("Qt/libexec/rcc")
+ data.extra_files.append("Qt/libexec/qt.conf")
+
+ # *.py
+ data.extra_dirs.append("support")
+
+ # pyside-tools with python backend
+ # Including the 'scripts' folder would include all the tools into the
+ # PySide6_Essentials wheel. The moment when we add a tool that has a
+ # dependency on a module in PySide6_AddOns, then we should split out
+ # the following line into individual subfolder and files, to better
+ # control which tool goes into which wheel
+ data.extra_dirs.append("scripts")
+
+ data.extra_dirs.append("typesystems/glue")
+
+ data.extra_files.append("__feature__.pyi")
+ data.extra_files.append("__init__.py")
+ data.extra_files.append("_git_pyside_version.py")
+ data.extra_files.append("_config.py")
+ data.extra_files.append("py.typed")
+
+ # Assistant
+ if sys.platform == "darwin":
+ data.extra_dirs.append("Assistant.app")
+ else:
+ data.extra_files.append("assistant*")
+ data.translations.append("assistant_*")
+
+ # Linguist
+ if sys.platform == "darwin":
+ data.extra_dirs.append("Linguist.app")
+ else:
+ data.extra_files.append("linguist*")
+ data.extra_files.append("lconvert*")
+ data.translations.append("linguist_*")
+
+ data.extra_files.append("lrelease*")
+ data.extra_files.append("lupdate*")
+
+ # General translations
+ data.translations.append("qtbase_*")
+ data.translations.append("qt_help_*")
+ data.translations.append("qt_*")
+
+ # Extra libraries
+ data.qtlib.append("libicudata*")
+ data.qtlib.append("libicui18n*")
+ data.qtlib.append("libicule*")
+ data.qtlib.append("libiculx*")
+ data.qtlib.append("libicutest*")
+ data.qtlib.append("libicutu*")
+ data.qtlib.append("libicuuc*")
+ data.qtlib.append("libicuio*")
+
+ return data
+
+
+def module_QtGui() -> ModuleData:
+ data = ModuleData("Gui")
+ _typesystems = [
+ "gui_common.xml",
+ "typesystem_gui_common.xml",
+ "typesystem_gui_mac.xml",
+ "typesystem_gui_win.xml",
+ "typesystem_gui_x11.xml",
+ "typesystem_gui_rhi.xml"
+ ]
+
+ _metatypes = [
+ "qt6eglfsdeviceintegrationprivate_relwithdebinfo_metatypes.json",
+ "qt6eglfskmssupportprivate_relwithdebinfo_metatypes.json",
+ "qt6kmssupportprivate_relwithdebinfo_metatypes.json",
+ "qt6xcbqpaprivate_relwithdebinfo_metatypes.json",
+ ]
+
+ _qtlib = [
+ "libQt6EglFSDeviceIntegration",
+ "libQt6EglFsKmsSupport",
+ "libQt6XcbQpa",
+ ]
+
+ data.typesystems.extend(_typesystems)
+ data.metatypes.extend(_metatypes)
+ data.qtlib.extend(_qtlib)
+
+ json_data = get_module_json_data("Gui")
+ data.plugins = get_module_plugins(json_data)
+ data.extra_files.append("Qt/plugins/platforms/libqeglfs*")
+
+ return data
+
+
+def module_QtWidgets() -> ModuleData:
+ data = ModuleData("Widgets")
+ data.typesystems.append("widgets_common.xml")
+ data.typesystems.append("typesystem_widgets_common.xml")
+
+ if sys.platform == "win32":
+ data.extra_files.append("uic.exe")
+ else:
+ data.extra_files.append("Qt/libexec/uic")
+ json_data = get_module_json_data("Widgets")
+ data.plugins = get_module_plugins(json_data)
+
+ return data
+
+
+def module_QtHelp() -> ModuleData:
+ data = ModuleData("Help")
+
+ return data
+
+
+def module_QtNetwork() -> ModuleData:
+ data = ModuleData("Network")
+ json_data = get_module_json_data("Network")
+ data.plugins = get_module_plugins(json_data)
+
+ return data
+
+
+def module_QtBluetooth() -> ModuleData:
+ data = ModuleData("Bluetooth")
+ data.translations.append("qtconnectivity_*")
+
+ return data
+
+
+def module_QtConcurrent() -> ModuleData:
+ data = ModuleData("Concurrent")
+
+ return data
+
+
+def module_QtDBus() -> ModuleData:
+ data = ModuleData("DBus")
+
+ return data
+
+
+def module_QtDesigner() -> ModuleData:
+ data = ModuleData("Designer")
+ data.qtlib.append("libQt6DesignerComponents")
+ data.metatypes.append("qt6designercomponentsprivate_relwithdebinfo_metatypes.json")
+ json_data = get_module_json_data("Designer")
+ data.plugins = get_module_plugins(json_data)
+ data.extra_files.append("Qt/plugins/assetimporters/libuip*")
+
+ # Designer
+ if sys.platform == "darwin":
+ data.extra_dirs.append("Designer.app")
+ else:
+ data.extra_files.append("designer*")
+ data.translations.append("designer_*")
+
+ return data
+
+
+def module_QtNfc() -> ModuleData:
+ data = ModuleData("Nfc")
+
+ return data
+
+
+def module_QtPdf() -> ModuleData:
+ data = ModuleData("Pdf")
+ data.qtlib.append("libQt6PdfQuick")
+
+ return data
+
+
+def module_QtPdfWidgets() -> ModuleData:
+ data = ModuleData("PdfWidgets")
+
+ return data
+
+
+def module_QtPrintSupport() -> ModuleData:
+ data = ModuleData("PrintSupport")
+ data.typesystems.append("typesystem_printsupport_common.xml")
+ json_data = get_module_json_data("PrintSupport")
+ data.plugins = get_module_plugins(json_data)
+
+ return data
+
+
+def module_QtQml() -> ModuleData:
+ data = ModuleData("Qml")
+ json_data = get_module_json_data("Qml")
+ data.plugins = get_module_plugins(json_data)
+ json_data = get_module_json_data("QmlCompilerPrivate")
+ data.plugins += get_module_plugins(json_data)
+
+ _qtlib = [
+ "libQt6LabsAnimation",
+ "libQt6LabsFolderListModel",
+ "libQt6LabsQmlModels*",
+ "libQt6LabsSettings",
+ "libQt6LabsSharedImage",
+ "libQt6LabsWavefrontMesh",
+ "libQt6QmlCore",
+ "libQt6QmlLocalStorage",
+ "libQt6QmlModels",
+ "libQt6QmlNetwork",
+ "libQt6QmlWorkerScript",
+ "libQt6QmlXmlListModel",
+ "libQt6QmlCompiler"
+ ]
+
+ _include = [
+ "pysideqml.h",
+ "pysideqmlmacros.h",
+ "pysideqmlregistertype.h",
+ ]
+
+ _metatypes = [
+ "qt6labsanimation_relwithdebinfo_metatypes.json",
+ "qt6labsfolderlistmodel_relwithdebinfo_metatypes.json",
+ "qt6labsqmlmodels_relwithdebinfo_metatypes.json",
+ "qt6labssettings_relwithdebinfo_metatypes.json",
+ "qt6labssharedimage_relwithdebinfo_metatypes.json",
+ "qt6labswavefrontmesh_relwithdebinfo_metatypes.json",
+ "qt6packetprotocolprivate_relwithdebinfo_metatypes.json",
+ "qt6qmlcompilerprivate_relwithdebinfo_metatypes.json",
+ "qt6qmlcompilerplusprivate_relwithdebinfo_metatypes.json",
+ "qt6qmlcore_relwithdebinfo_metatypes.json",
+ "qt6qmldebugprivate_relwithdebinfo_metatypes.json",
+ "qt6qmldomprivate_relwithdebinfo_metatypes.json",
+ "qt6qmllintprivate_relwithdebinfo_metatypes.json",
+ "qt6qmllocalstorage_relwithdebinfo_metatypes.json",
+ "qt6qmlmodels_relwithdebinfo_metatypes.json",
+ "qt6qmlworkerscript_relwithdebinfo_metatypes.json",
+ "qt6qmlxmllistmodel_relwithdebinfo_metatypes.json",
+ ]
+
+ _qml = [
+ "Qt/labs/animation",
+ "Qt/labs/folderlistmodel",
+ "Qt/labs/sharedimage",
+ "Qt/labs/wavefrontmesh",
+ "Qt/labs/qmlmodels",
+ "Qt/labs/platform",
+ "Qt/labs/settings",
+ ]
+
+ data.lib.append("libpyside6qml")
+ json_data = get_module_json_data("Qml")
+ data.plugins = get_module_plugins(json_data)
+ data.translations.append("qtdeclarative_*")
+ if sys.platform == "win32":
+ data.extra_files.append("pyside6qml.*.lib")
+ data.extra_files.append("pyside6qml.*.dll")
+ data.extra_files.append("qml/builtins.qmltypes")
+ data.extra_files.append("qml/jsroot.qmltypes")
+ data.extra_files.append("qmlimportscanner.exe")
+ data.extra_files.append("qmltyperegistrar.exe")
+ data.extra_files.append("qmlcachegen.exe")
+ else:
+ data.extra_files.append("Qt/qml/builtins.qmltypes")
+ data.extra_files.append("Qt/qml/jsroot.qmltypes")
+ data.extra_files.append("Qt/libexec/qmlimportscanner")
+ data.extra_files.append("Qt/libexec/qmltyperegistrar")
+ data.extra_files.append("Qt/libexec/qmlcachegen")
+
+ data.qtlib.extend(_qtlib)
+ data.include.extend(_include)
+ data.metatypes.extend(_metatypes)
+ data.qml.extend(_qml)
+
+ data.extra_files.append("qmllint*")
+ data.extra_files.append("qmlformat*")
+ data.extra_files.append("qmlls*")
+
+ return data
+
+
+def module_QtQuick() -> ModuleData:
+ data = ModuleData("Quick")
+ _metatypes = [
+ "qt6quickcontrolstestutilsprivate_relwithdebinfo_metatypes.json",
+ "qt6quickdialogs2_relwithdebinfo_metatypes.json",
+ "qt6quickdialogs2quickimpl_relwithdebinfo_metatypes.json",
+ "qt6quickdialogs2utils_relwithdebinfo_metatypes.json",
+ "qt6quickeffectsprivate_relwithdebinfo_metatypes.json",
+ "qt6quicketest_relwithdebinfo_metatypes.json",
+ "qt6quicketestutilsprivate_relwithdebinfo_metatypes.json",
+ "qt6quicklayouts_relwithdebinfo_metatypes.json",
+ "qt6quickparticlesprivate_relwithdebinfo_metatypes.json",
+ "qt6quickshapesprivate_relwithdebinfo_metatypes.json",
+ "qt6quicktemplates2_relwithdebinfo_metatypes.json",
+ "qt6quicktest_relwithdebinfo_metatypes.json",
+ "qt6quicktestutilsprivate_relwithdebinfo_metatypes.json",
+ "qt6quicktimeline_relwithdebinfo_metatypes.json",
+ ]
+ _qtlib = [
+ "libQt6QuickEffects",
+ "libQt6QuickDialogs2",
+ "libQt6QuickDialogs2QuickImpl",
+ "libQt6QuickDialogs2Utils",
+ "libQt6QuickLayouts",
+ "libQt6QuickParticles",
+ "libQt6QuickShapes",
+ "libQt6QuickTemplates2",
+ "libQt6QuickTest",
+ "libQt6QuickTimeline",
+ "libQt6QuickTimelineBlendTrees",
+ ]
+
+ # Adding GraphicalEffects files
+ data.qml.append("Qt5Compat/GraphicalEffects")
+
+ data.qtlib.extend(_qtlib)
+ data.metatypes.extend(_metatypes)
+ json_data = get_module_json_data("Quick")
+ data.plugins = get_module_plugins(json_data)
+
+ return data
+
+
+def module_QtQuickControls2() -> ModuleData:
+ data = ModuleData("QuickControls2")
+ data.qtlib.append("libQt6QuickControls2")
+ data.qtlib.append("libQt6QuickControls2Basic")
+ data.qtlib.append("libQt6QuickControls2BasicStyleImpl")
+ data.qtlib.append("libQt6QuickControls2Fusion")
+ data.qtlib.append("libQt6QuickControls2FusionStyleImpl")
+ data.qtlib.append("libQt6QuickControls2Imagine")
+ data.qtlib.append("libQt6QuickControls2ImagineStyleImpl")
+ data.qtlib.append("libQt6QuickControls2Impl")
+ data.qtlib.append("libQt6QuickControls2Material")
+ data.qtlib.append("libQt6QuickControls2MaterialStyleImpl")
+ data.qtlib.append("libQt6QuickControls2Universal")
+ data.qtlib.append("libQt6QuickControls2UniversalStyleImpl")
+ if sys.platform == "win32":
+ data.qtlib.append("libQt6QuickControls2WindowsStyleImpl")
+ elif sys.platform == "darwin":
+ data.qtlib.append("libQt6QuickControls2IOSStyleImpl")
+ data.qtlib.append("libQt6QuickControls2MacOSStyleImpl")
+
+ data.metatypes.append("qt6quickcontrols2impl_relwithdebinfo_metatypes.json")
+
+ return data
+
+
+def module_QtQuickTest() -> ModuleData:
+ data = ModuleData("QuickTest")
+
+ return data
+
+
+def module_QtQuickWidgets() -> ModuleData:
+ data = ModuleData("QuickWidgets")
+ return data
+
+
+def module_QtXml() -> ModuleData:
+ data = ModuleData("Xml")
+ return data
+
+
+def module_QtTest() -> ModuleData:
+ data = ModuleData("Test")
+ return data
+
+
+def module_QtSql() -> ModuleData:
+ data = ModuleData("Sql")
+ json_data = get_module_json_data("Sql")
+ data.plugins = get_module_plugins(json_data)
+
+ return data
+
+
+def module_QtSvg() -> ModuleData:
+ data = ModuleData("Svg")
+
+ return data
+
+
+def module_QtSvgWidgets() -> ModuleData:
+ data = ModuleData("SvgWidgets")
+
+ return data
+
+
+def module_QtTextToSpeech() -> ModuleData:
+ data = ModuleData("TextToSpeech")
+ json_data = get_module_json_data("TextToSpeech")
+ data.plugins = get_module_plugins(json_data)
+
+ return data
+
+
+def module_QtUiTools() -> ModuleData:
+ data = ModuleData("UiTools")
+
+ return data
+
+
+def module_QtWayland() -> ModuleData:
+ data = ModuleData("Wayland")
+
+ _qtlib = [
+ "libQt6WaylandClient",
+ "libQt6WaylandCompositor",
+ "libQt6WaylandEglClientHwIntegration",
+ "libQt6WaylandEglCompositorHwIntegration",
+ "libQt6WlShellIntegration",
+ ]
+
+ _metatypes = [
+ "qt6waylandclient_relwithdebinfo_metatypes.json",
+ "qt6waylandeglclienthwintegrationprivate_relwithdebinfo_metatypes.json",
+ "qt6wlshellintegrationprivate_relwithdebinfo_metatypes.json",
+ ]
+
+ data.qtlib.extend(_qtlib)
+ data.metatypes.extend(_metatypes)
+ json_data = get_module_json_data("WaylandClient")
+ data.plugins = get_module_plugins(json_data)
+ json_data = get_module_json_data("WaylandCompositor")
+ data.plugins += get_module_plugins(json_data)
+ return data
+
+
+def module_Qt3DCore() -> ModuleData:
+ data = ModuleData("3DCore", qml=["Qt3D/Core"])
+
+ return data
+
+
+def module_Qt3DAnimation() -> ModuleData:
+ data = ModuleData("3DAnimation", qml=["Qt3D/Animation"])
+
+ return data
+
+
+def module_Qt3DExtras() -> ModuleData:
+ data = ModuleData("3DExtras", qml=["Qt3D/Extras"])
+
+ return data
+
+
+def module_Qt3DInput() -> ModuleData:
+ data = ModuleData("3DInput", qml=["Qt3D/Input"])
+ json_data = get_module_json_data("3DInput")
+ data.plugins = get_module_plugins(json_data)
+
+ return data
+
+
+def module_Qt3DLogic() -> ModuleData:
+ data = ModuleData("3DLogic", qml=["Qt3D/Logic"])
+
+ return data
+
+
+def module_Qt3DRender() -> ModuleData:
+ data = ModuleData("3DRender", qml=["Qt3D/Render"])
+ json_data = get_module_json_data("3DRender")
+ data.plugins = get_module_plugins(json_data)
+
+ return data
+
+
+def module_QtQuick3D() -> ModuleData:
+ data = ModuleData("Quick3D")
+
+ _qtlib = [
+ "libQt6Quick3DAssetImport",
+ "libQt6Quick3DAssetUtils",
+ "libQt6Quick3DEffects",
+ "libQt6Quick3DGlslParser",
+ "libQt6Quick3DHelpers",
+ "libQt6Quick3DHelpersImpl",
+ "libQt6Quick3DIblBaker",
+ "libQt6Quick3DParticleEffects",
+ "libQt6Quick3DParticles",
+ "libQt6Quick3DPhysics",
+ "libQt6Quick3DPhysicsHelpers",
+ "libQt6Quick3DRuntimeRender",
+ "libQt6Quick3DSpatialAudio",
+ "libQt6Quick3DUtils",
+ "libQt6ShaderTools",
+ "libQt63DQuick",
+ "libQt63DQuickAnimation",
+ "libQt63DQuickExtras",
+ "libQt63DQuickExtras",
+ "libQt63DQuickInput",
+ "libQt63DQuickRender",
+ "libQt63DQuickScene2D",
+ ]
+
+ _metatypes = [
+ "qt63dquick_relwithdebinfo_metatypes.json",
+ "qt63dquickanimation_relwithdebinfo_metatypes.json",
+ "qt63dquickextras_relwithdebinfo_metatypes.json",
+ "qt63dquickinput_relwithdebinfo_metatypes.json",
+ "qt63dquickrender_relwithdebinfo_metatypes.json",
+ "qt63dquickscene2d_relwithdebinfo_metatypes.json",
+ "qt6quick3dassetimport_relwithdebinfo_metatypes.json",
+ "qt6quick3dassetutils_relwithdebinfo_metatypes.json",
+ "qt6quick3deffects_relwithdebinfo_metatypes.json",
+ "qt6quick3dglslparserprivate_relwithdebinfo_metatypes.json",
+ "qt6quick3dhelpers_relwithdebinfo_metatypes.json",
+ "qt6quick3diblbaker_relwithdebinfo_metatypes.json",
+ "qt6quick3dparticleeffects_relwithdebinfo_metatypes.json",
+ "qt6quick3dparticles_relwithdebinfo_metatypes.json",
+ "qt6quick3druntimerender_relwithdebinfo_metatypes.json",
+ "qt6quick3dutils_relwithdebinfo_metatypes.json",
+ "qt6shadertools_relwithdebinfo_metatypes.json",
+ ]
+
+ json_data = get_module_json_data("Quick3DAssetImport")
+ data.plugins = get_module_plugins(json_data)
+ data.qtlib.extend(_qtlib)
+ data.metatypes.extend(_metatypes)
+ data.extra_files.append("Qt/plugins/assetimporters/libassimp*")
+ data.extra_files.append("qsb*")
+ data.extra_files.append("balsam*")
+
+ return data
+
+
+def module_QtAxContainer() -> ModuleData:
+ data = ModuleData("AxContainer")
+ if sys.platform == "win32":
+ data.metatypes.append("qt6axbaseprivate_metatypes.json")
+ data.metatypes.append("qt6axserver_metatypes.json")
+
+ return data
+
+
+def module_QtWebEngineCore() -> ModuleData:
+ data = ModuleData("WebEngineCore", qml=["QtWebEngine"])
+ data.translations.append("qtwebengine_locales/*")
+ data.translations.append("qtwebengine_*")
+ data.extra_dirs.append("Qt/resources")
+ if sys.platform == "win32":
+ data.extra_files.append("resources/qtwebengine*.pak")
+ data.extra_files.append("resources/v8_context_snapshot*.*")
+ data.extra_files.append("QtWebEngineProcess.exe")
+ else:
+ data.extra_files.append("Qt/libexec/QtWebEngineProcess")
+
+ return data
+
+
+def module_QtWebEngineWidgets() -> ModuleData:
+ data = ModuleData("WebEngineWidgets")
+
+ return data
+
+
+def module_QtWebEngineQuick() -> ModuleData:
+ data = ModuleData("WebEngineQuick")
+ data.qtlib.append("libQt6WebEngineQuickDelegatesQml")
+ data.metatypes.append("qt6webenginequickdelegatesqml_relwithdebinfo_metatypes.json")
+
+ return data
+
+
+def module_QtCharts() -> ModuleData:
+ data = ModuleData("Charts")
+ data.qtlib.append("libQt6ChartsQml")
+ data.metatypes.append("qt6chartsqml_relwithdebinfo_metatypes.json")
+
+ return data
+
+
+def module_QtDataVisualization() -> ModuleData:
+ data = ModuleData("DataVisualization")
+ data.qtlib.append("libQt6DataVisualizationQml")
+ data.metatypes.append("qt6datavisualizationqml_relwithdebinfo_metatypes.json")
+ data.typesystems.append("datavisualization_common.xml")
+
+ return data
+
+
+def module_QtGraphs() -> ModuleData:
+ data = ModuleData("Graphs")
+
+ return data
+
+
+def module_QtMultimedia() -> ModuleData:
+ data = ModuleData("Multimedia")
+ data.qtlib.append("libQt6MultimediaQuick")
+ data.metatypes.append("qt6multimediaquickprivate_relwithdebinfo_metatypes.json")
+
+ json_data = get_module_json_data("Multimedia")
+ data.translations.append("qtmultimedia_*")
+ data.plugins = get_module_plugins(json_data)
+
+ if sys.platform == "win32":
+ data.extra_files.extend(["avcodec-60.dll", "avformat-60.dll", "avutil-58.dll",
+ "swresample-4.dll", "swscale-7.dll"])
+
+ return data
+
+
+def module_QtMultimediaWidgets() -> ModuleData:
+ data = ModuleData("MultimediaWidgets")
+
+ return data
+
+
+def module_QtNetworkAuth() -> ModuleData:
+ data = ModuleData("NetworkAuth")
+
+ return data
+
+
+def module_QtPositioning() -> ModuleData:
+ data = ModuleData("Positioning")
+ data.qtlib.append("libQt6PositioningQuick")
+ data.metatypes.append("qt6positioningquick_relwithdebinfo_metatypes.json")
+ json_data = get_module_json_data("Positioning")
+ data.plugins = get_module_plugins(json_data)
+
+ return data
+
+
+def module_QtRemoteObjects() -> ModuleData:
+ data = ModuleData("RemoteObjects")
+ data.qtlib.append("libQt6RemoteObjectsQml")
+ data.metatypes.append("qt6remoteobjectsqml_relwithdebinfo_metatypes.json")
+
+ return data
+
+
+def module_QtSensors() -> ModuleData:
+ data = ModuleData("Sensors")
+ data.qtlib.append("libQt6SensorsQuick")
+ data.metatypes.append("qt6sensorsquick_relwithdebinfo_metatypes.json")
+ json_data = get_module_json_data("Sensors")
+ data.plugins = get_module_plugins(json_data)
+
+ return data
+
+
+def module_QtSerialPort() -> ModuleData:
+ data = ModuleData("SerialPort")
+ data.translations.append("qtserialport_*")
+
+ return data
+
+
+def module_QtSpatialAudio() -> ModuleData:
+ data = ModuleData("SpatialAudio")
+ data.metatypes.append("qt6spatialaudio_debug_metatypes.json")
+
+ return data
+
+
+def module_QtStateMachine() -> ModuleData:
+ data = ModuleData("StateMachine")
+ data.qtlib.append("libQt6StateMachineQml")
+ data.metatypes.append("qt6statemachineqml_relwithdebinfo_metatypes.json")
+
+ return data
+
+
+def module_QtScxml() -> ModuleData:
+ data = ModuleData("Scxml")
+ data.qtlib.append("libQt6ScxmlQml")
+ data.metatypes.append("qt6scxmlqml_relwithdebinfo_metatypes.json")
+ json_data = get_module_json_data("Scxml")
+ data.plugins = get_module_plugins(json_data)
+
+ return data
+
+
+def module_QtWebChannel() -> ModuleData:
+ data = ModuleData("WebChannel")
+ data.qtlib.append("libQt6WebChannelQuick")
+
+ return data
+
+
+def module_QtWebSockets() -> ModuleData:
+ data = ModuleData("WebSockets")
+ data.translations.append("qtwebsockets_*")
+
+ return data
+
+
+def module_QtOpenGL() -> ModuleData:
+ data = ModuleData("OpenGL")
+ _typesystems = [
+ "opengl_common.xml",
+ "typesystem_glgeti_v_includes.xml",
+ "typesystem_glgeti_v_modifications.xml",
+ "typesystem_glgetv_includes.xml",
+ "typesystem_glgetv_modifications.xml",
+ "typesystem_opengl_modifications1_0.xml",
+ "typesystem_opengl_modifications1_0_compat.xml",
+ "typesystem_opengl_modifications1_1.xml",
+ "typesystem_opengl_modifications1_1_compat.xml",
+ "typesystem_opengl_modifications1_2_compat.xml",
+ "typesystem_opengl_modifications1_3_compat.xml",
+ "typesystem_opengl_modifications1_4.xml",
+ "typesystem_opengl_modifications1_4_compat.xml",
+ "typesystem_opengl_modifications2_0.xml",
+ "typesystem_opengl_modifications2_0_compat.xml",
+ "typesystem_opengl_modifications2_1.xml",
+ "typesystem_opengl_modifications3_0.xml",
+ "typesystem_opengl_modifications3_3.xml",
+ "typesystem_opengl_modifications3_3a.xml",
+ "typesystem_opengl_modifications4_0.xml",
+ "typesystem_opengl_modifications4_1.xml",
+ "typesystem_opengl_modifications4_3.xml",
+ "typesystem_opengl_modifications4_4.xml",
+ "typesystem_opengl_modifications4_4_core.xml",
+ "typesystem_opengl_modifications4_5.xml",
+ "typesystem_opengl_modifications4_5_core.xml",
+ "typesystem_opengl_modifications_va.xml",
+ ]
+
+ data.typesystems.extend(_typesystems)
+ if sys.platform == "win32":
+ data.extra_files.append("opengl32*.dll")
+
+ return data
+
+
+def module_QtOpenGLWidgets() -> ModuleData:
+ data = ModuleData("OpenGLWidgets")
+ return data
+
+
+def module_QtSerialBus() -> ModuleData:
+ data = ModuleData("SerialBus")
+ json_data = get_module_json_data("SerialBus")
+ data.plugins = get_module_plugins(json_data)
+ return data
+
+
+def module_QtVirtualKeyboard() -> ModuleData:
+ data = ModuleData("VirtualKeyboard")
+ data.plugins.append("virtualkeyboard")
+ return data
+
+
+def module_QtHttpServer() -> ModuleData:
+ data = ModuleData("HttpServer")
+ return data
+
+
+def module_QtLanguageServer() -> ModuleData:
+ data = ModuleData("LanguageServer")
+ data.metatypes.append("qt6languageserverprivate_relwithdebinfo_metatypes.json")
+ return data
+
+
+def module_QtJsonRpc() -> ModuleData:
+ data = ModuleData("JsonRpc")
+ data.metatypes.append("qt6jsonrpcprivate_relwithdebinfo_metatypes.json")
+ return data
+
+
+def module_QtLocation() -> ModuleData:
+ data = ModuleData("Location")
+ json_data = get_module_json_data("Location")
+ data.plugins = get_module_plugins(json_data)
+ data.translations.append("qtlocation_*")
+ return data
+
+
+def module_QtAsyncio() -> ModuleData:
+ data = ModuleData("Asyncio")
+ data.extra_dirs.append("QtAsyncio")
+ return data
+
+
+def module_QtExampleIcons() -> ModuleData:
+ data = ModuleData("ExampleIcons")
+ return data
diff --git a/build_scripts/wheel_override.py b/build_scripts/wheel_override.py
index 03c9c92ab..f3f9f17a9 100644
--- a/build_scripts/wheel_override.py
+++ b/build_scripts/wheel_override.py
@@ -1,95 +1,66 @@
-#############################################################################
-##
-## Copyright (C) 2018 The Qt Company Ltd.
-## Contact: https://www.qt.io/licensing/
-##
-## This file is part of Qt for Python.
-##
-## $QT_BEGIN_LICENSE:LGPL$
-## Commercial License Usage
-## Licensees holding valid commercial Qt licenses may use this file in
-## accordance with the commercial license agreement provided with the
-## Software or, alternatively, in accordance with the terms contained in
-## a written agreement between you and The Qt Company. For licensing terms
-## and conditions see https://www.qt.io/terms-conditions. For further
-## information use the contact form at https://www.qt.io/contact-us.
-##
-## GNU Lesser General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU Lesser
-## General Public License version 3 as published by the Free Software
-## Foundation and appearing in the file LICENSE.LGPL3 included in the
-## packaging of this file. Please review the following information to
-## ensure the GNU Lesser General Public License version 3 requirements
-## will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
-##
-## GNU General Public License Usage
-## Alternatively, this file may be used under the terms of the GNU
-## General Public License version 2.0 or (at your option) the GNU General
-## Public license version 3 or any later version approved by the KDE Free
-## Qt Foundation. The licenses are as published by the Free Software
-## Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
-## included in the packaging of this file. Please review the following
-## information to ensure the GNU General Public License requirements will
-## be met: https://www.gnu.org/licenses/gpl-2.0.html and
-## https://www.gnu.org/licenses/gpl-3.0.html.
-##
-## $QT_END_LICENSE$
-##
-#############################################################################
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+import platform
+import sys
+from pathlib import Path
+from email.generator import Generator
+
+from .log import log
+from .options import OPTION, CommandMixin
+from .utils import is_64bit
+from .wheel_utils import get_package_version, get_qt_version, macos_plat_name
+
wheel_module_exists = False
+
try:
- import os
- import sys
- from distutils import log as logger
- from wheel import pep425tags
+ from packaging import tags
+ from wheel import __version__ as wheel_version
from wheel.bdist_wheel import bdist_wheel as _bdist_wheel
+ from wheel.bdist_wheel import get_abi_tag, get_platform
from wheel.bdist_wheel import safer_name as _safer_name
- from wheel.pep425tags import get_abbr_impl, get_impl_ver, get_abi_tag
- from wheel.pep425tags import get_platform as wheel_get_platform
- from email.generator import Generator
- from wheel import __version__ as wheel_version
-
- from .options import OPTION
wheel_module_exists = True
except Exception as e:
- _bdist_wheel, wheel_version = type, '' # dummy to make class statement happy
- print('***** Exception while trying to prepare bdist_wheel override class: {}. '
- 'Skipping wheel overriding.'.format(e))
+ _bdist_wheel, wheel_version = type, "" # dummy to make class statement happy
+ log.warning(f"***** Exception while trying to prepare bdist_wheel override class: {e}. "
+ "Skipping wheel overriding.")
+
+
+def get_bdist_wheel_override():
+ return PysideBuildWheel if wheel_module_exists else None
-def get_bdist_wheel_override(params):
- if wheel_module_exists:
- class PysideBuildWheelDecorated(PysideBuildWheel):
- def __init__(self, *args, **kwargs):
- self.params = params
- PysideBuildWheel.__init__(self, *args, **kwargs)
- return PysideBuildWheelDecorated
- else:
- return None
+class PysideBuildWheel(_bdist_wheel, CommandMixin):
+ user_options = (_bdist_wheel.user_options + CommandMixin.mixin_user_options
+ if wheel_module_exists else None)
-class PysideBuildWheel(_bdist_wheel):
def __init__(self, *args, **kwargs):
- self.pyside_params = None
+ self.command_name = "bdist_wheel"
+ self._package_version = None
_bdist_wheel.__init__(self, *args, **kwargs)
+ CommandMixin.__init__(self)
def finalize_options(self):
+ CommandMixin.mixin_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 = self.params['macos_plat_name']
+ self.plat_name = macos_plat_name()
# When limited API is requested, notify bdist_wheel to
- # create a properly named package.
- limited_api_enabled = OPTION["LIMITED_API"] and sys.version_info[0] >= 3
+ # create a properly named package, which will contain
+ # the initial cpython version we support.
+ limited_api_enabled = OPTION["LIMITED_API"] == 'yes'
if limited_api_enabled:
- self.py_limited_api = "cp35.cp36.cp37.cp38"
+ self.py_limited_api = "cp37"
+
+ self._package_version = get_package_version()
_bdist_wheel.finalize_options(self)
@@ -98,23 +69,114 @@ class PysideBuildWheel(_bdist_wheel):
# 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 = self.params['qt_version']
- package_version = self.params['package_version']
- wheel_version = "{}-{}".format(package_version, qt_version)
+ # PySide6-6.3-6.3.2-cp36-abi3-macosx_10_10_intel.whl
+ # The PySide6 version is "6.3".
+ # The Qt version built against is "6.3.2".
+ wheel_version = f"{self._package_version}-{get_qt_version()}"
components = (_safer_name(self.distribution.get_name()), wheel_version)
if self.build_number:
components += (self.build_number,)
return '-'.join(components)
- # Copy of get_tag from bdist_wheel.py, to allow setting a
- # multi-python impl tag, by removing an assert. Otherwise we
- # would have to rename wheels manually for limited api
- # packages. Also we set "none" abi tag on Windows, because
- # pip does not yet support "abi3" tag, leading to
- # installation failure when tried.
+ # Modify the returned wheel tag tuple to use correct python version
+ # info when cross-compiling. We use the python info extracted from
+ # the shiboken python config test.
+ # setuptools / wheel don't support cross compiling out of the box
+ # at the moment. Relevant discussion at
+ # https://discuss.python.org/t/towards-standardizing-cross-compiling/10357
+ def get_cross_compiling_tag_tuple(self, tag_tuple):
+ (old_impl, old_abi_tag, plat_name) = tag_tuple
+
+ # Compute tag from the python version that the build command
+ # queried.
+ build_command = self.get_finalized_command('build')
+ python_target_info = build_command.python_target_info['python_info']
+
+ impl = 'no-py-ver-impl-available'
+ abi = 'no-abi-tag-info-available'
+ py_version = python_target_info['version'].split('.')
+ py_version_major, py_version_minor, _ = py_version
+
+ so_abi = python_target_info['so_abi']
+ if so_abi and so_abi.startswith('cpython-'):
+ interpreter_name, cp_version = so_abi.split('-')[:2]
+ impl_name = tags.INTERPRETER_SHORT_NAMES.get(interpreter_name) or interpreter_name
+ impl_ver = f"{py_version_major}{py_version_minor}"
+ impl = impl_name + impl_ver
+ abi = f'cp{cp_version}'
+ tag_tuple = (impl, abi, plat_name)
+ return tag_tuple
+
+ # Adjust wheel tag for limited api and cross compilation.
+ @staticmethod
+ def adjust_cross_compiled_many_linux_tag(old_tag):
+ (old_impl, old_abi_tag, old_plat_name) = old_tag
+
+ new_plat_name = old_plat_name
+
+ # TODO: Detect glibc version instead. We're abusing the
+ # manylinux2014 tag here, just like we did with manylinux1
+ # for x86_64 builds.
+ many_linux_prefix = 'manylinux2014'
+ linux_prefix = "linux_"
+ if old_plat_name.startswith(linux_prefix):
+ # Extract the arch suffix like -armv7l or -aarch64
+ _index = old_plat_name.index(linux_prefix) + len(linux_prefix)
+ plat_name_arch_suffix = old_plat_name[_index:]
+
+ new_plat_name = f"{many_linux_prefix}_{plat_name_arch_suffix}"
+
+ tag = (old_impl, old_abi_tag, new_plat_name)
+ return tag
+
+ # Adjust wheel tag for limited api and cross compilation.
+ def adjust_tag_and_supported_tags(self, old_tag, supported_tags):
+ tag = old_tag
+ (old_impl, old_abi_tag, old_plat_name) = old_tag
+
+ # Get new tag for cross builds.
+ if self.is_cross_compile:
+ tag = self.get_cross_compiling_tag_tuple(old_tag)
+
+ # Use PEP600 for manylinux wheel name
+ # For Qt6 we know RHEL 8.4 is the base linux platform,
+ # and has GLIBC 2.28.
+ # This will generate a name that contains:
+ # manylinux_2_28
+ # TODO: Add actual distro detection, instead of
+ # relying on limited_api option.
+ if (old_plat_name in ('linux-x86_64', 'linux_x86_64')
+ and is_64bit()
+ and self.py_limited_api):
+ _, _version = platform.libc_ver()
+ glibc = _version.replace(".", "_")
+ tag = (old_impl, old_abi_tag, f"manylinux_{glibc}_x86_64")
+
+ # Set manylinux tag for cross-compiled builds when targeting
+ # limited api.
+ if self.is_cross_compile and self.py_limited_api:
+ tag = self.adjust_cross_compiled_many_linux_tag(tag)
+
+ # Reset the abi name and python versions supported by this wheel
+ # when targeting limited API. This is the same code that's
+ # in get_tag(), but done later after our own customizations.
+ if self.py_limited_api and old_impl.startswith('cp3'):
+ (_, _, adjusted_plat_name) = tag
+ impl = self.py_limited_api
+ abi_tag = 'abi3'
+ tag = (impl, abi_tag, adjusted_plat_name)
+
+ # If building for limited API or we created a new tag, add it
+ # to the list of supported tags.
+ if tag != old_tag or self.py_limited_api:
+ supported_tags.append(tag)
+ return tag
+
+ # A slightly modified copy of get_tag from bdist_wheel.py, to allow
+ # adjusting the returned tag without triggering an assert. Otherwise
+ # we would have to rename wheels manually.
+ # Copy is up-to-date since commit
+ # 0acd203cd896afec7f715aa2ff5980a403459a3b in the wheel repo.
def get_tag(self):
# bdist sets self.plat_name if unset, we should only use it for purepy
# wheels if the user supplied it.
@@ -123,64 +185,51 @@ class PysideBuildWheel(_bdist_wheel):
elif self.root_is_pure:
plat_name = 'any'
else:
- plat_name = self.plat_name or wheel_get_platform()
- if plat_name in ('linux-x86_64', 'linux_x86_64') and sys.maxsize == 2147483647:
+ # macosx contains system version in platform name so need special handle
+ if self.plat_name and not self.plat_name.startswith("macosx"):
+ plat_name = self.plat_name
+ else:
+ # on macOS always limit the platform name to comply with any
+ # c-extension modules in bdist_dir, since the user can specify
+ # a higher MACOSX_DEPLOYMENT_TARGET via tools like CMake
+
+ # on other platforms, and on macOS if there are no c-extension
+ # modules, use the default platform name.
+ plat_name = get_platform(self.bdist_dir)
+
+ if plat_name in ('linux-x86_64', 'linux_x86_64') and not is_64bit():
plat_name = 'linux_i686'
- # To allow uploading to pypi, we need the wheel name
- # to contain 'manylinux1'.
- # The wheel which will be uploaded to pypi will be
- # built on RHEL7, so it doesn't completely qualify for
- # manylinux1 support, but it's the minimum requirement
- # for building Qt. We only enable this for x64 limited
- # api builds (which are the only ones uploaded to
- # pypi).
- # TODO: Add actual distro detection, instead of
- # relying on limited_api option.
- if (plat_name in ('linux-x86_64', 'linux_x86_64')
- and sys.maxsize > 2147483647
- and (self.py_limited_api or sys.version_info[0] == 2)):
- plat_name = 'manylinux1_x86_64'
- plat_name = plat_name.replace('-', '_').replace('.', '_')
+ plat_name = plat_name.lower().replace('-', '_').replace('.', '_')
if self.root_is_pure:
if self.universal:
- impl = 'py2.py3'
+ impl = 'py3'
else:
impl = self.python_tag
tag = (impl, 'none', plat_name)
else:
- impl_name = get_abbr_impl()
- impl_ver = get_impl_ver()
+ impl_name = tags.interpreter_name()
+ impl_ver = tags.interpreter_version()
impl = impl_name + impl_ver
# We don't work on CPython 3.1, 3.0.
if self.py_limited_api and (impl_name + impl_ver).startswith('cp3'):
impl = self.py_limited_api
- abi_tag = "abi3" if sys.platform != "win32" else "none"
+ abi_tag = 'abi3'
else:
abi_tag = str(get_abi_tag()).lower()
tag = (impl, abi_tag, plat_name)
- try:
- supported_tags = pep425tags.get_supported(
- supplied_platform=plat_name if self.plat_name_supplied else None)
- except TypeError:
- # This was breaking the CI, specifically the:
- # OpenSUSE 15 x86_64 using ICC
- # Some versions of Python 2.7 require an argument called
- # 'archive_root' which doesn't exist on 3, so we set it to
- # 'None' for those version (e.g.: Python 2.7.14)
- supported_tags = pep425tags.get_supported(None,
- supplied_platform=plat_name if self.plat_name_supplied else None)
- # XXX switch to this alternate implementation for non-pure:
- if (self.py_limited_api) or (plat_name in ('manylinux1_x86_64') and sys.version_info[0] == 2):
- return tag
- assert tag == supported_tags[0], "%s != %s" % (tag, supported_tags[0])
- assert tag in supported_tags, ("would build wheel with unsupported tag {}".format(tag))
+ # issue gh-374: allow overriding plat_name
+ supported_tags = [(t.interpreter, t.abi, plat_name)
+ for t in tags.sys_tags()]
+ # PySide's custom override.
+ tag = self.adjust_tag_and_supported_tags(tag, supported_tags)
+ assert tag in supported_tags, (f"would build wheel with unsupported tag {tag}")
return tag
# Copy of get_tag from bdist_wheel.py, to write a triplet Tag
# only once for the limited_api case.
- def write_wheelfile(self, wheelfile_base, generator='bdist_wheel (' + wheel_version + ')'):
+ def write_wheelfile(self, wheelfile_base, generator=f'bdist_wheel ({wheel_version})'):
from email.message import Message
msg = Message()
msg['Wheel-Version'] = '1.0' # of the spec
@@ -192,7 +241,7 @@ class PysideBuildWheel(_bdist_wheel):
# Doesn't work for bdist_wininst
impl_tag, abi_tag, plat_tag = self.get_tag()
# To enable pypi upload we are adjusting the wheel name
- pypi_ready = (OPTION["LIMITED_API"] and sys.version_info[0] >= 3) or (sys.version_info[0] == 2)
+ pypi_ready = True if OPTION["LIMITED_API"] else False
def writeTag(impl):
for abi in abi_tag.split('.'):
@@ -204,8 +253,8 @@ class PysideBuildWheel(_bdist_wheel):
for impl in impl_tag.split('.'):
writeTag(impl)
- wheelfile_path = os.path.join(wheelfile_base, 'WHEEL')
- logger.info('creating %s', wheelfile_path)
+ wheelfile_path = Path(wheelfile_base) / 'WHEEL'
+ log.info(f'creating {wheelfile_path}')
with open(wheelfile_path, 'w') as f:
Generator(f, maxheaderlen=0).flatten(msg)
diff --git a/build_scripts/wheel_utils.py b/build_scripts/wheel_utils.py
new file mode 100644
index 000000000..5ec26c742
--- /dev/null
+++ b/build_scripts/wheel_utils.py
@@ -0,0 +1,124 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+import time
+from pathlib import Path
+from sysconfig import get_config_var, get_platform
+
+from packaging.version import parse as parse_version
+from setuptools.errors import SetupError
+
+from .options import OPTION
+from .qtinfo import QtInfo
+from .utils import memoize, parse_cmake_conf_assignments_by_key
+from . import PYSIDE
+
+
+@memoize
+def get_package_timestamp():
+ """ In a Coin CI build the returned timestamp will be the
+ Coin integration id timestamp. For regular builds it's
+ just the current timestamp or a user provided one."""
+ option_value = OPTION["PACKAGE_TIMESTAMP"]
+ return option_value if option_value else int(time.time())
+
+
+def get_qt_version():
+ qtinfo = QtInfo()
+ qt_version = qtinfo.version
+
+ if not qt_version:
+ raise SetupError("Failed to query the Qt version with qmake {qtinfo.qmake_command}")
+
+ if parse_version(qtinfo.version) < parse_version("5.7"):
+ raise SetupError(f"Incompatible Qt version detected: {qt_version}. "
+ "A Qt version >= 5.7 is required.")
+
+ return qt_version
+
+
+@memoize
+def get_package_version():
+ """ Returns the version string for the PySide6 package. """
+ setup_script_dir = Path.cwd()
+ pyside_project_dir = setup_script_dir / "sources" / PYSIDE
+ d = parse_cmake_conf_assignments_by_key(pyside_project_dir)
+ major_version = d['pyside_MAJOR_VERSION']
+ minor_version = d['pyside_MINOR_VERSION']
+ patch_version = d['pyside_MICRO_VERSION']
+
+ final_version = f"{major_version}.{minor_version}.{patch_version}"
+ release_version_type = d.get('pyside_PRE_RELEASE_VERSION_TYPE')
+ pre_release_version = d.get('pyside_PRE_RELEASE_VERSION')
+
+ if release_version_type and not release_version_type.startswith("comm") and pre_release_version:
+ final_version = f"{final_version}{release_version_type}{pre_release_version}"
+ if release_version_type and release_version_type.startswith("comm"):
+ final_version = f"{final_version}+{release_version_type}"
+
+ # Add the current timestamp to the version number, to suggest it
+ # is a development snapshot build.
+ if OPTION["SNAPSHOT_BUILD"]:
+ final_version = f"{final_version}.dev{get_package_timestamp()}"
+ return final_version
+
+
+def macos_qt_min_deployment_target():
+ target = QtInfo().macos_min_deployment_target
+
+ if not target:
+ raise SetupError("Failed to query for Qt's QMAKE_MACOSX_DEPLOYMENT_TARGET.")
+ return target
+
+
+@memoize
+def macos_pyside_min_deployment_target():
+ """
+ Compute and validate PySide6 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 = 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:
+ # macOS Big Sur returns a number not a string
+ python_target_split = [int(x) for x in str(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 SetupError(message.format(setup_target, "Python", python_target))
+ if setup_target_split < qt_target_split:
+ raise SetupError(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
+
+
+@memoize
+def macos_plat_name():
+ deployment_target = macos_pyside_min_deployment_target()
+ # Example triple "macosx-10.12-x86_64".
+ plat = get_platform().split("-")
+ plat_name = f"{plat[0]}-{deployment_target}-{plat[2]}"
+ return plat_name