diff options
author | Alexandru Croitor <alexandru.croitor@qt.io> | 2021-09-29 19:01:51 +0200 |
---|---|---|
committer | Alexandru Croitor <alexandru.croitor@qt.io> | 2022-02-04 15:51:04 +0100 |
commit | 57866a57586d401c784f809f9f7994b0e4623706 (patch) | |
tree | 48b9d5a7d1a03d1edd15359858adcefd18430467 /build_scripts/utils.py | |
parent | 14e4527cc427ce8c5e7c1758a95a1bbce0498471 (diff) |
setup.py: Add support for cross-building
setup.py can now be used to cross-compile PySide to a target Linux
distribution from a Linux host.
For example you could cross-compile PySide targeting an arm64
Raspberry Pi4 sysroot on an Ubuntu x86_64 host machine.
Cross-compiling PySide has a few requirements:
- a sysroot to cross-compile against, with a pre-installed Qt,
Python interpreter, library and development packages (which
provides C++ headers)
- a host Qt installation of the same version that is in the target
sysroot
- a host Python installation, preferably of the same version as the
target one (to run setup.py)
- a working cross-compiling toolchain (cross-compiler, linker, etc)
- a custom written CMake toolchain file
- CMake version 3.17+
- Qt version 6.3+
The CMake toolchain file is required to set up all the relevant
cross-compilation information: where the sysroot is, where the
toolchain is, the compiler name, compiler flags, etc.
Once are requirements are met, to cross-compile one has to specify a
few additional options when calling setup.py: the path to the cmake
toolchain file, the path to the host Qt installation
and the target python platform name.
An example setup.py invocation to build a wheel for an armv7 machine
might look like the following:
python setup.py bdist_wheel --parallel=8 --ignore-git --reuse-build
--cmake-toolchain-file=$PWD/rpi/toolchain_armv7.cmake
--qt-host-path=/opt/Qt/6.3.0/gcc_64
--plat-name=linux_armv7l
--limited-api=yes
--standalone
Sample platform names that can be used are: linux_armv7, linux_aarch64.
If the auto-detection code fails to find the target Python or Qt
installation, one can specify their location by providing the
--python-target-path=<path>
and
--qt-target-path=<path>
options to setup.py.
If the automatic build of the host shiboken code generator fails,
one can specify the path to a custom built host shiboken via the
--shiboken-host-path option.
Documentation about the build process and a sample CMake
toolchain file will be added in a separate change.
Implementation details.
Internally, setup.py will build a host shiboken executable using
the provided host Qt path, and then use it for the cross-build.
This is achieved via an extra setup.py sub-invocation with some
heuristics on which options should be passed to the sub-invocation.
The host shiboken is not included in the target wheels.
Introspection of where the host / target Qt and Python are located
is done via CMake compile tests, because we can't query information
from a qmake that is built for a different architecture / platform.
When limited API is enabled, we modify the wheel name to contain the
manylinux2014 tag, despite the wheel not fully qualifying for that
tag.
When copying the Qt libraries / plugins from the target sysroot in a
standalone build, we need to adjust all their rpaths to match the
destination directory layout of the wheel.
Fixes: PYSIDE-802
Task-number: PYSIDE-1033
Change-Id: I6e8c51ef5127d85949de650396d615ca95194db0
Reviewed-by: Cristian Maureira-Fredes <cristian.maureira-fredes@qt.io>
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
Diffstat (limited to 'build_scripts/utils.py')
-rw-r--r-- | build_scripts/utils.py | 78 |
1 files changed, 75 insertions, 3 deletions
diff --git a/build_scripts/utils.py b/build_scripts/utils.py index 39cff6743..2daad642e 100644 --- a/build_scripts/utils.py +++ b/build_scripts/utils.py @@ -48,6 +48,8 @@ import subprocess import fnmatch import itertools import glob +import tempfile +from collections import defaultdict import urllib.request as urllib @@ -244,12 +246,16 @@ def init_msvc_env(platform_arch, build_type): log.info("Done initializing MSVC env") -def platform_cmake_options(): +def platform_cmake_options(as_tuple_list=False): result = [] if sys.platform == 'win32': # Prevent cmake from auto-detecting clang if it is in path. - result.append("-DCMAKE_C_COMPILER=cl.exe") - result.append("-DCMAKE_CXX_COMPILER=cl.exe") + 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 @@ -1250,3 +1256,69 @@ def parse_cmake_conf_assignments_by_key(source_dir): d[key] = value return d + +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_string = ' '.join(cmd) + # FIXME Python 3.7: Use subprocess.run() + proc = subprocess.Popen(cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + shell=False, + cwd=build_path, + universal_newlines=True) + output, error = proc.communicate() + proc.wait() + return_code = proc.returncode + + if return_code != 0: + raise RuntimeError(f"\nFailed to configure CMake project \n " + f"'{project_path}' \n with error: \n {error}\n " + f"Return code: {return_code}\n" + f"Configure args were:\n {cmd_string}") + + if clean_temp_dir: + rmtree(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 |