diff options
Diffstat (limited to 'build_scripts/main.py')
-rw-r--r-- | build_scripts/main.py | 199 |
1 files changed, 117 insertions, 82 deletions
diff --git a/build_scripts/main.py b/build_scripts/main.py index 4660c9879..9a8d4fb3f 100644 --- a/build_scripts/main.py +++ b/build_scripts/main.py @@ -10,11 +10,10 @@ import sysconfig import time from packaging.version import parse as parse_version from pathlib import Path -from shutil import 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. @@ -28,8 +27,7 @@ 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 .log import log +from .log import log, LogLevel from setuptools.errors import SetupError from .build_info_collector import BuildInfoCollectorMixin @@ -38,8 +36,8 @@ 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, which) @@ -52,11 +50,11 @@ 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,18 +76,14 @@ def _get_make(platform_arch, build_type): if makespec == "make": return ("make", "Unix Makefiles") if makespec == "msvc": - nmake_path = Path(which("nmake")) - if nmake_path is None or not nmake_path.exists(): - log.info("nmake not found. Trying to initialize the MSVC env...") - init_msvc_env(platform_arch, build_type) - nmake_path = Path(which("nmake")) - if not nmake_path or not nmake_path.exists(): - raise SetupError('"nmake" could not be found.') 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." @@ -184,12 +178,12 @@ class PysideInstall(_install, CommandMixin): 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): @@ -288,7 +282,7 @@ class PysideBuild(_build, CommandMixin, 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 @@ -576,14 +570,22 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): # Build module cmake_cmd = [str(OPTION["CMAKE"])] - if OPTION["QUIET"]: + 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, @@ -594,6 +596,8 @@ class PysideBuild(_build, CommandMixin, 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]}", + f"-DQUIET_BUILD={cmake_quiet_build}", + f"-DCMAKE_RULE_MESSAGES={cmake_rule_messages}", str(module_src_dir) ] @@ -607,9 +611,9 @@ class PysideBuild(_build, CommandMixin, 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(): @@ -631,6 +635,7 @@ class PysideBuild(_build, CommandMixin, 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(','): @@ -669,10 +674,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): else: 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']: @@ -688,10 +690,15 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): else: raise SetupError("option limited-api must be 'yes' or 'no' " "(default yes if applicable, i.e. Python " - "version >= 3.7 and release build if on Windows)") + "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'] @@ -717,6 +724,9 @@ class PysideBuild(_build, CommandMixin, 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() @@ -731,8 +741,7 @@ class PysideBuild(_build, CommandMixin, 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.7") + cmake_cmd.append("-DUSE_PYTHON_VERSION=3.9") cmake_cmd += platform_cmake_options() @@ -763,7 +772,7 @@ class PysideBuild(_build, CommandMixin, 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. @@ -774,7 +783,7 @@ class PysideBuild(_build, CommandMixin, 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"]: @@ -800,7 +809,7 @@ class PysideBuild(_build, CommandMixin, 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 OPTION["SHIBOKEN_HOST_PATH"].exists()): + or not Path(OPTION["SHIBOKEN_HOST_PATH"]).exists()): raise SetupError("Please specify the location of host shiboken tools via " "--shiboken-host-path=") @@ -828,7 +837,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): 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 SetupError(f"Error compiling {extension}") @@ -846,11 +855,10 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): if found: log.info("Generating Shiboken documentation") make_doc_cmd = [str(self.make_path), "doc"] - if OPTION["VERBOSE_BUILD"] and self.make_generator == "Ninja": + 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("Error generating documentation " - f"for {extension}") + raise SetupError(f"Error generating documentation for {extension}") else: log.info("Sphinx not found, skipping documentation build") else: @@ -878,13 +886,13 @@ class PysideBuild(_build, CommandMixin, 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, @@ -901,6 +909,8 @@ class PysideBuild(_build, CommandMixin, 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, @@ -994,8 +1004,8 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): # (lib/libclang.lib), whereas we want to copy the shared # library (bin/libclang.dll). clang_lib_path = Path(re.sub(r'lib/libclang.lib$', - 'bin/libclang.dll', - clang_lib_path)) + 'bin/libclang.dll', + clang_lib_path)) else: clang_lib_path = Path(clang_lib_path) # shiboken6 links against libclang.so.6 or a similarly @@ -1047,7 +1057,7 @@ class PysideBuild(_build, CommandMixin, 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.warning(f"No shared library filters found for platform {self.plat_name}. " @@ -1059,29 +1069,31 @@ class PysideBuild(_build, CommandMixin, 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 = Path(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 @@ -1099,6 +1111,8 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): override=override) elif sys.platform == 'darwin': + message = "Updated rpath in" + def rpath_cmd(srcpath): final_rpath = '' # Command line rpath option takes precedence over @@ -1116,15 +1130,13 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): raise RuntimeError(f"Not configured for platform {sys.platform}") # Update rpath - for srcname in executables: - srcpath = Path(package_path) / srcname - if srcpath.is_dir() or srcpath.is_symlink(): + for executable in executables: + if executable.is_dir() or executable.is_symlink(): continue - if not srcpath.exists(): + 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, @@ -1144,7 +1156,6 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): log.info("Patching rpath for Qt and QML plugins.") for plugin in plugin_paths: - plugin = Path(plugin) if plugin.is_dir() or plugin.is_symlink(): continue if not plugin.exists(): @@ -1162,7 +1173,7 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): 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. @@ -1174,29 +1185,27 @@ class PysideBuild(_build, CommandMixin, BuildInfoCollectorMixin): 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 = [qt_lib_dir / lib for lib in libs] - for library in lib_paths: + for library in self.package_libraries(qt_lib_dir): if library.is_dir() or library.is_symlink(): continue - if library.exists(): + 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, CommandMixin): - description = "Build .rst documentation only" +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) 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(): @@ -1206,8 +1215,13 @@ class PysideRstDocs(Command, CommandMixin): 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: @@ -1215,7 +1229,6 @@ class PysideRstDocs(Command, CommandMixin): log.info(f"-- Documentation directory: 'html/{PYSIDE}/'") else: raise SetupError("Sphinx not found - aborting") - self.html_dir = Path("html") # creating directories html/pyside6/shiboken6 try: @@ -1245,13 +1258,35 @@ class PysideRstDocs(Command, CommandMixin): "-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 SetupError(f"Error running CMake for {self.doc_dir}") if self.name == PYSIDE: - self.sphinx_src = 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 @@ -1276,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() |