diff options
Diffstat (limited to 'build_scripts/main.py')
-rw-r--r-- | build_scripts/main.py | 458 |
1 files changed, 248 insertions, 210 deletions
diff --git a/build_scripts/main.py b/build_scripts/main.py index 806a957e4..9a8d4fb3f 100644 --- a/build_scripts/main.py +++ b/build_scripts/main.py @@ -6,14 +6,14 @@ import os import platform import re import sys +import sysconfig import time from packaging.version import parse as parse_version from pathlib import Path -from shutil import which, copytree +from shutil import copytree, rmtree from textwrap import dedent # PYSIDE-1760: Pre-load setuptools modules early to avoid racing conditions. -# Please be careful: All setuptools modules must be loaded before _distutils # 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. @@ -21,42 +21,40 @@ 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 -# Use the distutils implementation within setuptools (but not before) -from setuptools._distutils import log -from setuptools._distutils import sysconfig as sconfig -from setuptools._distutils.command.build import build as _build -from setuptools._distutils.errors import DistutilsSetupError +from .log import log, LogLevel +from setuptools.errors import SetupError from .build_info_collector import BuildInfoCollectorMixin from .config import config -from .options import OPTION, DistUtilsCommandMixin +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, filter_match, - get_numpy_location, get_python_dict, init_msvc_env, +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) -from .versions import PYSIDE, PYSIDE_MODULE, SHIBOKEN + 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 = os.getcwd() -build_scripts_dir = os.path.join(setup_script_dir, 'build_scripts') -setup_py_path = os.path.join(setup_script_dir, "setup.py") +setup_script_dir = Path.cwd() +build_scripts_dir = setup_script_dir / 'build_scripts' +setup_py_path = setup_script_dir / "setup.py" -start_time = int(time.time()) +start_time = time.time() def elapsed(): - return int(time.time()) - start_time + return int(time.time() - start_time) def get_setuptools_extension_modules(): @@ -78,39 +76,35 @@ def _get_make(platform_arch, build_type): if makespec == "make": return ("make", "Unix Makefiles") if makespec == "msvc": - nmake_path = which("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 = which("nmake") - if not nmake_path or not os.path.exists(nmake_path): - raise DistutilsSetupError('"nmake" could not be found.') if not OPTION["NO_JOM"]: - jom_path = which("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 DistutilsSetupError(msg) + raise SetupError(msg) return (nmake_path, "NMake Makefiles") if makespec == "mingw": - return ("mingw32-make", "mingw32-make") + return (Path("mingw32-make"), "mingw32-make") if makespec == "ninja": - return ("ninja", "Ninja") - raise DistutilsSetupError(f'Invalid option --make-spec "{makespec}".') + 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 os.path.isabs(make_path): - found_path = which(make_path) - if not found_path or not os.path.exists(found_path): + 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 DistutilsSetupError(m) + raise SetupError(m) make_path = found_path return (make_path, make_generator) @@ -161,35 +155,35 @@ def prepare_build(): if install_prefix.endswith("qtbase"): qt_src_dir = install_prefix else: # SDK: Use 'Src' directory - maybe_qt_src_dir = os.path.join(os.path.dirname(install_prefix), 'Src', 'qtbase') - if os.path.exists(maybe_qt_src_dir): + 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, DistUtilsCommandMixin): +class PysideInstall(_install, CommandMixin): - user_options = _install.user_options + DistUtilsCommandMixin.mixin_user_options + user_options = _install.user_options + CommandMixin.mixin_user_options def __init__(self, *args, **kwargs): self.command_name = "install" _install.__init__(self, *args, **kwargs) - DistUtilsCommandMixin.__init__(self) + CommandMixin.__init__(self) def initialize_options(self): _install.initialize_options(self) def finalize_options(self): - DistUtilsCommandMixin.mixin_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 @@ -241,7 +235,7 @@ class PysideBuildPy(_build_py): # _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): @@ -255,28 +249,28 @@ class PysideInstallLib(_install_lib): 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(f"'{self.build_dir}' does not exist -- no Python modules to install") return return outfiles -class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): +class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): - user_options = _build.user_options + DistUtilsCommandMixin.mixin_user_options + user_options = _build.user_options + CommandMixin.mixin_user_options def __init__(self, *args, **kwargs): self.command_name = "build" _build.__init__(self, *args, **kwargs) - DistUtilsCommandMixin.__init__(self) + CommandMixin.__init__(self) BuildInfoCollectorMixin.__init__(self) def finalize_options(self): os_name_backup = os.name - DistUtilsCommandMixin.mixin_finalize_options(self) + CommandMixin.mixin_finalize_options(self) BuildInfoCollectorMixin.collect_and_assign(self) use_os_name_hack = False @@ -288,7 +282,7 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): 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 @@ -351,13 +345,13 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): and parse_version(self.qtinfo.version) >= parse_version("5.7.0")): clang_dir, clang_source = detect_clang() if clang_dir: - clangBinDir = os.path.join(clang_dir, 'bin') - if clangBinDir not in os.environ.get('PATH'): + 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) @@ -368,18 +362,18 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): # Save the shiboken build dir path for clang deployment # purposes. - self.shiboken_build_dir = os.path.join(self.build_dir, SHIBOKEN) + self.shiboken_build_dir = self.build_dir / SHIBOKEN self.log_pre_build_info() # Prepare folders - if not os.path.exists(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): + 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): + if not self.install_dir.exists(): log.info(f"Creating install folder {self.install_dir}...") os.makedirs(self.install_dir) @@ -388,7 +382,7 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): # 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(self.install_dir) + 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()): @@ -400,10 +394,10 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): # 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(self.build_dir, file=f) print(self.build_classifiers, file=f) @@ -437,14 +431,14 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): if _project is not None: if not _wheel_path.exists(): - _wheel_path.mkdir() + _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.warn(f'***** Found directory "{_dst}", removing it first.') + log.warning(f'Found directory "{_dst}", removing it first.') remove_tree(_dst) try: @@ -452,8 +446,8 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): # is used when using the 'install' setup.py instruction. copytree(_src, _dst) except Exception as e: - log.warn(f'***** problem renaming "{self.st_build_dir}"') - log.warn(f'ignored error: {type(e).__name__}: {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.") log.info(f"--- Build completed ({elapsed()}s)") @@ -462,7 +456,7 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): if config.is_internal_shiboken_generator_build_and_part_of_top_level_all(): return - setuptools_install_prefix = sconfig.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) @@ -524,16 +518,13 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): return 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) + 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++", f"{module_src_dir}/patchelf.cc", "-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"]: @@ -553,13 +544,13 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): # Prepare folders os.chdir(self.build_dir) - module_build_dir = os.path.join(self.build_dir, extension) - skipflag_file = f"{module_build_dir} -skip" - if os.path.exists(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(f"Deleting module build folder {module_build_dir}...") @@ -570,23 +561,31 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): log.error(f'ignored error: {e}') else: log.info(f"Reusing module build folder {module_build_dir}...") - if not os.path.exists(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, @@ -597,7 +596,9 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): # 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]}", - module_src_dir + f"-DQUIET_BUILD={cmake_quiet_build}", + f"-DCMAKE_RULE_MESSAGES={cmake_rule_messages}", + str(module_src_dir) ] # When cross-compiling we set Python_ROOT_DIR to tell @@ -610,14 +611,14 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): # 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}") + 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(): config_dir = OPTION["SHIBOKEN_CONFIG_DIR"] - if os.path.exists(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: @@ -634,6 +635,7 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): module_sub_set += ';' module_sub_set += m cmake_cmd.append(f"-DMODULES={module_sub_set}") + if OPTION["SKIP_MODULES"]: skip_modules = '' for m in OPTION["SKIP_MODULES"].split(','): @@ -670,12 +672,9 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): if numpy: cmake_cmd.append(f"-DNUMPY_INCLUDE_DIR={numpy}") else: - log.warn('***** numpy include directory was not found.') + log.warning('numpy include directory was not found.') - if self.build_type.lower() == 'debug': - if not self.is_cross_compile: - cmake_cmd.append(f"-DPYTHON_DEBUG_LIBRARY={self.py_library}") - else: + if self.build_type.lower() != 'debug': if OPTION['NO_STRIP']: cmake_cmd.append("-DQFP_NO_STRIP=1") if OPTION['NO_OVERRIDE_OPTIMIZATION_FLAGS']: @@ -686,13 +685,20 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): 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.6)") + 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["DISABLE_PYI"]: + cmake_cmd.append("-DDISABLE_PYI=yes") - if OPTION["VERBOSE_BUILD"]: + 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'] @@ -705,7 +711,7 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): 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() == PYSIDE: pyside_qt_conf_prefix = '' @@ -718,6 +724,9 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): 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() @@ -732,8 +741,7 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): cmake_cmd.append(f"-DPACKAGE_SETUP_PY_PACKAGE_TIMESTAMP={timestamp}") if extension.lower() in [SHIBOKEN]: - cmake_cmd.append("-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=yes") - cmake_cmd.append("-DUSE_PYTHON_VERSION=3.6") + cmake_cmd.append("-DUSE_PYTHON_VERSION=3.9") cmake_cmd += platform_cmake_options() @@ -764,7 +772,7 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): # 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. @@ -775,7 +783,7 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): os.environ['MACOSX_DEPLOYMENT_TARGET'] = deployment_target if OPTION["BUILD_DOCS"]: - # Build the whole documentation (rst + API) by default + # Build the whole documentation (Base + API) by default cmake_cmd.append("-DFULLDOCSBUILD=1") if OPTION["DOC_BUILD_ONLINE"]: @@ -801,9 +809,9 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): 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 os.path.exists(OPTION["SHIBOKEN_HOST_PATH"])): - raise DistutilsSetupError( - "Please specify the location of host shiboken tools via --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}") @@ -821,18 +829,18 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): if not OPTION["SKIP_CMAKE"]: log.info(f"Configuring module {extension} ({module_src_dir})...") if run_process(cmake_cmd) != 0: - raise DistutilsSetupError(f"Error configuring {extension}") + raise SetupError(f"Error configuring {extension}") else: log.info(f"Reusing old configuration for module {extension} ({module_src_dir})...") log.info(f"-- Compiling module {extension}...") - cmd_make = [self.make_path] + cmd_make = [str(self.make_path)] if OPTION["JOBS"]: cmd_make.append(OPTION["JOBS"]) - if OPTION["VERBOSE_BUILD"] and self.make_generator == "Ninja": + if OPTION["LOG_LEVEL"] == LogLevel.VERBOSE and self.make_generator == "Ninja": cmd_make.append("-v") if run_process(cmd_make) != 0: - raise DistutilsSetupError(f"Error compiling {extension}") + 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 . @@ -846,12 +854,11 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): found = importlib.util.find_spec("sphinx") if found: log.info("Generating Shiboken documentation") - make_doc_cmd = [self.make_path, "doc"] - if OPTION["VERBOSE_BUILD"] and self.make_generator == "Ninja": + 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 DistutilsSetupError("Error generating documentation " - f"for {extension}") + raise SetupError(f"Error generating documentation for {extension}") else: log.info("Sphinx not found, skipping documentation build") else: @@ -869,8 +876,8 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): 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(f"Error pseudo installing {extension}") + if run_process([str(self.make_path), target]) != 0: + raise SetupError(f"Error pseudo installing {extension}") else: log.info(f"Skipped installing module {extension}") @@ -879,13 +886,13 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): 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") + log.info("Preparing setup tools build directory.") _vars = { "site_packages_dir": self.site_packages_dir, "sources_dir": self.sources_dir, @@ -902,6 +909,8 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): "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, @@ -927,33 +936,31 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): # 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 os.path.isdir(self.st_build_dir): + 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.warn(f'***** problem removing "{self.st_build_dir}"') - log.warn(f'ignored error: {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) 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(f"{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): # 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'] = [] @@ -971,11 +978,11 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): """ 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) "-B", # Specifies the build dir - self.shiboken_build_dir + str(self.shiboken_build_dir) ] out = run_process_output(cmake_cmd) lines = [s.strip() for s in out] @@ -996,10 +1003,11 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): # 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: + 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 @@ -1011,26 +1019,26 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): # 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) + 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) + 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 = os.path.join(destination_dir, basename) + destination_path = destination_dir / basename # Need to modify permissions in case file is not writable # (a reinstall would cause a permission denied error). @@ -1049,11 +1057,11 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): 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: + elif 'linux' in self.plat_name or 'android' in self.plat_name: filters = unix_filters else: - log.warn(f"No shared library filters found for platform {self.plat_name}. " - f"The package might miss Qt libraries and plugins.") + 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 @@ -1061,29 +1069,31 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): 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""" - filters = self.get_shared_library_filters() - return [lib for lib in os.listdir( - package_path) if filter_match(lib, filters)] + 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)""" - filters = self.get_shared_library_filters() - libraries = [] - for dir_path, dir_names, file_names in os.walk(initial_path): - for name in file_names: - if filter_match(name, filters): - library_path = os.path.join(dir_path, name) - libraries.append(library_path) - return libraries - - def update_rpath(self, package_path, executables, libexec=False): + 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'): + def rpath_cmd(srcpath): final_rpath = '' # Command line rpath option takes precedence over @@ -1101,6 +1111,8 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): override=override) elif sys.platform == 'darwin': + message = "Updated rpath in" + def rpath_cmd(srcpath): final_rpath = '' # Command line rpath option takes precedence over @@ -1118,21 +1130,20 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): raise RuntimeError(f"Not configured for platform {sys.platform}") # Update rpath - for srcname in executables: - srcpath = os.path.join(package_path, srcname) - if os.path.isdir(srcpath) or os.path.islink(srcpath): + for executable in executables: + if executable.is_dir() or executable.is_symlink(): continue - if not os.path.exists(srcpath): + if not executable.exists(): continue - rpath_cmd(srcpath) - log.info("Patched rpath to '$ORIGIN/' (Linux) or " - f"updated rpath (OS/X) in {srcpath}.") + 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 @@ -1145,21 +1156,24 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): log.info("Patching rpath for Qt and QML plugins.") for plugin in plugin_paths: - if os.path.isdir(plugin) or os.path.islink(plugin): + if plugin.is_dir() or plugin.is_symlink(): continue - if not os.path.exists(plugin): + if not plugin.exists(): continue if is_qml_plugin: - plugin_dir = os.path.dirname(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 = os.path.join("$ORIGIN", rel_path_from_qml_plugin_qt_lib_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.info(f"Patched rpath to '{rpath_value}' in {plugin}.") + log.debug(f"Patched rpath to '{rpath_value}' in {plugin}.") def update_rpath_for_linux_qt_libraries(self, qt_lib_dir): # Ensure that Qt libs and ICU libs have $ORIGIN in their rpath. @@ -1168,101 +1182,125 @@ class PysideBuild(_build, DistUtilsCommandMixin, BuildInfoCollectorMixin): 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}.") - libs = self.package_libraries(qt_lib_dir) - lib_paths = [os.path.join(qt_lib_dir, lib) for lib in libs] - for library in lib_paths: - if os.path.isdir(library) or os.path.islink(library): + for library in self.package_libraries(qt_lib_dir): + if library.is_dir() or library.is_symlink(): continue - if not os.path.exists(library): + if not library.exists(): continue linux_fix_rpaths_for_library(self._patchelf_path, library, rpath_value, override=True) - log.info(f"Patched rpath to '{rpath_value}' in {library}.") + log.debug(f"Patched rpath to '{rpath_value}' in {library}.") -class PysideRstDocs(Command, DistUtilsCommandMixin): - description = "Build .rst documentation only" - user_options = DistUtilsCommandMixin.mixin_user_options +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_rst_docs" + self.command_name = "build_base_docs" Command.__init__(self, *args, **kwargs) - DistUtilsCommandMixin.__init__(self) + CommandMixin.__init__(self) def initialize_options(self): - log.info("-- This build process will not include the API documentation." + 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 = os.path.join(config.setup_script_dir, "sources") - self.doc_dir = os.path.join(self.doc_dir, self.name) - self.doc_dir = os.path.join(self.doc_dir, "doc") + 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 DistutilsSetupError("Sphinx not found - aborting") - self.html_dir = "html" + raise SetupError("Sphinx not found - aborting") # creating directories html/pyside6/shiboken6 try: - if not os.path.isdir(self.html_dir): - os.mkdir(self.html_dir) + if not self.html_dir.is_dir(): + self.html_dir.mkdir(parents=True) if self.name == SHIBOKEN: - out_pyside = os.path.join(self.html_dir, PYSIDE) - if not os.path.isdir(out_pyside): - os.mkdir(out_pyside) - out_shiboken = os.path.join(out_pyside, SHIBOKEN) - if not os.path.isdir(out_shiboken): - os.mkdir(out_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 = os.path.join(self.html_dir, PYSIDE) + self.out_dir = self.html_dir / PYSIDE except (PermissionError, FileExistsError): - raise DistutilsSetupError(f"Error while creating directories for {self.doc_dir}") + raise SetupError(f"Error while creating directories for {self.doc_dir}") def run(self): if not self.skip: cmake_cmd = [ - OPTION["CMAKE"], - "-S", self.doc_dir, - "-B", self.out_dir, + str(OPTION["CMAKE"]), + "-S", str(self.doc_dir), + "-B", str(self.out_dir), "-DDOC_OUTPUT_FORMAT=html", "-DFULLDOCSBUILD=0", ] - if OPTION["QUIET"]: - cmake_cmd.append('-DQUIET_BUILD=1') + + 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 DistutilsSetupError(f"Error running CMake for {self.doc_dir}") + raise SetupError(f"Error running CMake for {self.doc_dir}") if self.name == PYSIDE: - self.sphinx_src = os.path.join(self.out_dir, "rst") + 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", "-c", self.sphinx_src, - self.doc_dir, 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 DistutilsSetupError(f"Error running CMake for {self.doc_dir}") + 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): - DistUtilsCommandMixin.mixin_finalize_options(self) + CommandMixin.mixin_finalize_options(self) cmd_class_dict = { @@ -1273,7 +1311,7 @@ cmd_class_dict = { 'develop': PysideDevelop, 'install': PysideInstall, 'install_lib': PysideInstallLib, - 'build_rst_docs': PysideRstDocs, + 'build_base_docs': PysideBaseDocs, } if wheel_module_exists: pyside_bdist_wheel = get_bdist_wheel_override() |