aboutsummaryrefslogtreecommitdiffstats
path: root/build_scripts/build_info_collector.py
diff options
context:
space:
mode:
Diffstat (limited to 'build_scripts/build_info_collector.py')
-rw-r--r--build_scripts/build_info_collector.py311
1 files changed, 311 insertions, 0 deletions
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