diff --git a/coin/instructions/build.yaml b/coin/instructions/build.yaml index 36b3c6b0750..b61815d748a 100644 --- a/coin/instructions/build.yaml +++ b/coin/instructions/build.yaml @@ -44,7 +44,7 @@ instructions: userMessageOnFailure: "Failed to extract LLVM package, check logs." - type: ExecuteCommand command: >- - python3 -u {{.AgentWorkingDir}}/qt-creator/qt-creator/scripts/build.py + {{.Env.PYTHON_EXECUTABLE}} -u {{.AgentWorkingDir}}/qt-creator/qt-creator/scripts/build.py --build-type {{.Env.QTC_BUILD_TYPE}} --src {{.AgentWorkingDir}}/qt-creator/qt-creator --build {{.AgentWorkingDir}}/qt-creator/qt-creator_build @@ -59,11 +59,9 @@ instructions: maxTimeInSeconds: 36000 maxTimeBetweenOutput: 3600 userMessageOnFailure: "Failed to run build.py, check logs." - - type: ChangeDirectory - directory: "{{.AgentWorkingDir}}/build/tqtc-qtsdk/packaging_tools" - type: ExecuteCommand command: >- - python3 -m pipenv run python -u bld_sdktool.py + {{.Env.PYTHON_EXECUTABLE}} -u {{.AgentWorkingDir}}/qt-creator/qt-creator/scripts/build_sdktool.py --qt-url {{.Env.QTC_SDKTOOL_QT_BASE_URL}}{{.Env.QTC_SDKTOOL_QT_EXT}} --qt-build {{.AgentWorkingDir}}/build/sdktool/qt --src {{.AgentWorkingDir}}/qt-creator/qt-creator/src/tools/sdktool @@ -97,7 +95,7 @@ instructions: userMessageOnFailure: "Failed to extract LLVM package, check logs." - type: ExecuteCommand command: >- - python3 -u {{.AgentWorkingDir}}/qt-creator/qt-creator/scripts/build.py + {{.Env.PYTHON_EXECUTABLE}} -u {{.AgentWorkingDir}}/qt-creator/qt-creator/scripts/build.py --build-type {{.Env.QTC_BUILD_TYPE}} --src {{.AgentWorkingDir}}/qt-creator/qt-creator --build {{.AgentWorkingDir}}/qt-creator/qt-creator_build @@ -112,14 +110,12 @@ instructions: maxTimeInSeconds: 36000 maxTimeBetweenOutput: 3600 userMessageOnFailure: "Failed to run build.py, check logs." - - type: ChangeDirectory - directory: "{{.AgentWorkingDir}}/build/tqtc-qtsdk/packaging_tools" - type: EnvironmentVariable variableName: MACOSX_DEPLOYMENT_TARGET variableValue: "{{.Env.SDKTOOL_MACOSX_DEPLOYMENT_TARGET}}" - type: ExecuteCommand command: >- - python3 -m pipenv run python -u bld_sdktool.py + {{.Env.PYTHON_EXECUTABLE}} -u {{.AgentWorkingDir}}/qt-creator/qt-creator/scripts/build_sdktool.py --qt-url {{.Env.QTC_SDKTOOL_QT_BASE_URL}}{{.Env.QTC_SDKTOOL_QT_EXT}} --qt-build {{.AgentWorkingDir}}/build/sdktool/qt --src {{.AgentWorkingDir}}/qt-creator/qt-creator/src/tools/sdktool @@ -235,7 +231,7 @@ instructions: userMessageOnFailure: "Failed to extract LLVM package, check logs." - type: ExecuteCommand command: >- - python -u {{.AgentWorkingDir}}\qt-creator\qt-creator\scripts\build.py + {{.Env.PYTHON_EXECUTABLE}} -u {{.AgentWorkingDir}}\qt-creator\qt-creator\scripts\build.py --build-type {{.Env.QTC_BUILD_TYPE}} --src {{.AgentWorkingDir}}\qt-creator\qt-creator --build {{.AgentWorkingDir}}\qt-creator\qt-creator_build @@ -254,11 +250,9 @@ instructions: maxTimeInSeconds: 36000 maxTimeBetweenOutput: 3600 userMessageOnFailure: "Failed to run build.py, check logs." - - type: ChangeDirectory - directory: "{{.AgentWorkingDir}}\\build\\tqtc-qtsdk\\packaging_tools" - type: ExecuteCommand command: >- - python -m pipenv run python -u bld_sdktool.py + {{.Env.PYTHON_EXECUTABLE}} -u {{.AgentWorkingDir}}\qt-creator\qt-creator\scripts\build_sdktool.py --qt-url {{.Env.QTC_SDKTOOL_QT_BASE_URL}}{{.Env.QTC_SDKTOOL_QT_EXT}} --qt-build {{.AgentWorkingDir}}\build\sdktool\qt --src {{.AgentWorkingDir}}\qt-creator\qt-creator\src\tools\sdktool diff --git a/coin/instructions/common_environment.yaml b/coin/instructions/common_environment.yaml index eedae366ad4..e960b988c51 100644 --- a/coin/instructions/common_environment.yaml +++ b/coin/instructions/common_environment.yaml @@ -40,6 +40,9 @@ instructions: - type: EnvironmentVariable variableName: QTC_LLVM_POSTFIX variableValue: "-windows-vs2019_64.7z" + - type: EnvironmentVariable + variableName: PYTHON_EXECUTABLE + variableValue: "python" enable_if: condition: property property: target.os @@ -61,6 +64,17 @@ instructions: - type: EnvironmentVariable variableName: QTC_ICU_URL variableValue: "https://ci-files02-hki.ci.qt.io/packages/jenkins/development_releases/prebuilt/icu/prebuilt/56.1/icu-linux-g++-Rhel7.2-x64.7z" + - type: EnvironmentVariable + variableName: PYTHON_EXECUTABLE + variableValue: "python3" + # RHEL 8.10 comes with Python 3.6, we need explicitly python3.11 + - type: EnvironmentVariable + variableName: PYTHON_EXECUTABLE + variableValue: "python3.11" + enable_if: + condition: property + property: host.osVersion + contains_value: "RHEL" enable_if: condition: and conditions: @@ -87,6 +101,9 @@ instructions: - type: EnvironmentVariable variableName: QTC_ICU_URL variableValue: "https://ci-files02-hki.ci.qt.io/packages/jenkins/development_releases/prebuilt/icu/prebuilt/73.2/icu-linux-g++-Debian11.6-aarch64.7z" + - type: EnvironmentVariable + variableName: PYTHON_EXECUTABLE + variableValue: "python3" enable_if: condition: and conditions: @@ -107,6 +124,9 @@ instructions: - type: EnvironmentVariable variableName: QTC_LLVM_POSTFIX variableValue: "-macos-universal.7z" + - type: EnvironmentVariable + variableName: PYTHON_EXECUTABLE + variableValue: "python3" enable_if: condition: property property: target.os diff --git a/coin/instructions/provision.yaml b/coin/instructions/provision.yaml index c6075c8ecd9..0351950c363 100644 --- a/coin/instructions/provision.yaml +++ b/coin/instructions/provision.yaml @@ -15,66 +15,55 @@ instructions: - type: MakeDirectory directory: "{{.BuildDir}}" - type: ChangeDirectory - directory: "{{.BuildDir}}" - - type: ExecuteCommand - command: ["git", "clone", "--jobs={{.NumCPU}}", "--depth=50", "-b", "production", "git://{{.Env.QT_COIN_GIT_DAEMON}}/qt-project/qtsdk/tqtc-qtsdk","tqtc-qtsdk"] - maxTimeInSeconds: 600 - maxTimeBetweenOutput: 600 - userMessageOnFailure: "Failed to install tqtc-qtsdk, check logs" + directory: "{{.AgentWorkingDir}}/qt-creator/qt-creator/scripts" - type: Group instructions: - - type: ExecuteCommand - command: python tqtc-qtsdk/jenkins-templates/jenkins/scripts/pkg_bootstrap.py - maxTimeInSeconds: 36000 - maxTimeBetweenOutput: 3600 - userMessageOnFailure: "pkg_bootstrap.py failed" - enable_if: - condition: and - conditions: - - condition: property - property: host.os - equals_value: Windows - - type: Group - instructions: - - type: ExecuteCommand - command: python3 tqtc-qtsdk/jenkins-templates/jenkins/scripts/pkg_bootstrap.py - maxTimeInSeconds: 36000 - maxTimeBetweenOutput: 3600 - userMessageOnFailure: "pkg_bootstrap.py failed" - enable_if: - condition: and - conditions: - - condition: property - property: host.os - not_equals_value: Windows - - type: ChangeDirectory - directory: "{{.BuildDir}}/tqtc-qtsdk/packaging_tools" - - type: ExecuteCommand - command: "python3 -m pipenv run python -u install_qt.py --qt-path {{.BuildDir}}/qt_install_dir --base-url {{.Env.QTC_QT_BASE_URL}} --base-url-postfix={{.Env.QTC_QT_POSTFIX}} --icu7z {{.Env.QTC_ICU_URL}} {{.Env.QTC_QT_MODULES}}" - executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution - maxTimeInSeconds: 3600 - maxTimeBetweenOutput: 360 - userMessageOnFailure: "Failed to install qt, check logs." + - type: ExecuteCommand + command: >- + {{.Env.PYTHON_EXECUTABLE}} -u {{.AgentWorkingDir}}/qt-creator/qt-creator/scripts/install_qt.py + --qt-path {{.BuildDir}}/qt_install_dir + --base-url {{.Env.QTC_QT_BASE_URL}} + --base-url-postfix={{.Env.QTC_QT_POSTFIX}} + --icu7z {{.Env.QTC_ICU_URL}} + {{.Env.QTC_QT_MODULES}} + executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution + maxTimeInSeconds: 3600 + maxTimeBetweenOutput: 360 + userMessageOnFailure: "Failed to install qt, check logs." enable_if: condition: property property: host.os equals_value: Linux - - type: ExecuteCommand - command: "python3 -m pipenv run python -u install_qt.py --qt-path {{.BuildDir}}/qt_install_dir --base-url {{.Env.QTC_QT_BASE_URL}} --base-url-postfix={{.Env.QTC_QT_POSTFIX}} {{.Env.QTC_QT_MODULES}}" - executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution - maxTimeInSeconds: 3600 - maxTimeBetweenOutput: 360 - userMessageOnFailure: "Failed to install qt, check logs." + - type: Group + instructions: + - type: ExecuteCommand + command: >- + {{.Env.PYTHON_EXECUTABLE}} -u {{.AgentWorkingDir}}/qt-creator/qt-creator/scripts/install_qt.py + --qt-path {{.BuildDir}}/qt_install_dir + --base-url {{.Env.QTC_QT_BASE_URL}} + --base-url-postfix={{.Env.QTC_QT_POSTFIX}} + {{.Env.QTC_QT_MODULES}}" + executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution + maxTimeInSeconds: 3600 + maxTimeBetweenOutput: 360 + userMessageOnFailure: "Failed to install qt, check logs." enable_if: condition: property property: host.os equals_value: MacOS - - type: ExecuteCommand - command: "python -m pipenv run python -u install_qt.py --qt-path {{.BuildDir}}/qt_install_dir --base-url {{.Env.QTC_QT_BASE_URL}} --base-url-postfix={{.Env.QTC_QT_POSTFIX}} --opengl32sw7z https://ci-files02-hki.ci.qt.io/packages/jenkins/development_releases/prebuilt/llvmpipe/windows/opengl32sw-64.7z --d3dcompiler7z https://ci-files02-hki.ci.qt.io/packages/jenkins/development_releases/prebuilt/d3dcompiler/msvc2013/d3dcompiler_47-x64.7z --openssl7z https://ci-files02-hki.ci.qt.io/packages/jenkins/openssl/openssl_1.1.1d_prebuild_x64.7z {{.Env.QTC_QT_MODULES}}" - executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution - maxTimeInSeconds: 3600 - maxTimeBetweenOutput: 360 - userMessageOnFailure: "Failed to install qt, check logs." + - type: Group + instructions: + - type: ExecuteCommand + command: >- + {{.Env.PYTHON_EXECUTABLE}} -u {{.AgentWorkingDir}}\qt-creator\qt-creator\scripts\install_qt.py + --qt-path {{.BuildDir}}/qt_install_dir + --base-url {{.Env.QTC_QT_BASE_URL}} + --base-url-postfix={{.Env.QTC_QT_POSTFIX}} + {{.Env.QTC_QT_MODULES}} + executeCommandArgumentSplitingBehavior: SplitAfterVariableSubstitution + maxTimeInSeconds: 3600 + maxTimeBetweenOutput: 360 + userMessageOnFailure: "Failed to install qt, check logs." enable_if: condition: and conditions: diff --git a/scripts/build.py b/scripts/build.py index 037ac37a545..9739c08204c 100755 --- a/scripts/build.py +++ b/scripts/build.py @@ -10,20 +10,13 @@ import collections import os import shlex import shutil +import sys import common def existing_path(path): return path if os.path.exists(path) else None -def default_python3(): - path_system = os.path.join('/usr', 'bin') if not common.is_windows_platform() else None - path = os.environ.get('PYTHON3_PATH') or path_system - postfix = '.exe' if common.is_windows_platform() else '' - return (path if not path - else (existing_path(os.path.join(path, 'python3' + postfix)) or - existing_path(os.path.join(path, 'python' + postfix)))) - def get_arguments(): parser = argparse.ArgumentParser(description='Build Qt Creator for packaging') parser.add_argument('--src', help='path to sources', required=True) @@ -52,7 +45,7 @@ def get_arguments(): help='Path to python libraries for use by cdbextension (Windows)') parser.add_argument('--python3', help='File path to python3 executable for generating translations', - default=default_python3()) + default=sys.executable) parser.add_argument('--no-qtcreator', help='Skip Qt Creator build (only build separate tools)', @@ -324,7 +317,7 @@ def package_qtcreator(args, paths): app], signed_install_path) if not args.no_dmg: - common.check_print_call(['python', '-u', + common.check_print_call([args.python3, '-u', os.path.join(paths.src, 'scripts', 'makedmg.py'), 'qt-creator' + args.zip_infix + '.dmg', 'Qt Creator', diff --git a/scripts/build_sdktool.py b/scripts/build_sdktool.py new file mode 100755 index 00000000000..b028079cafa --- /dev/null +++ b/scripts/build_sdktool.py @@ -0,0 +1,193 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +from __future__ import annotations +import argparse +from itertools import islice +import os +from pathlib import Path +from typing import NamedTuple + +from common import (is_linux_platform, is_mac_platform, is_windows_platform, + download_and_extract, check_print_call) + + +class BuildParams(NamedTuple): + src_path: Path + build_path: Path + target_path: Path + make_command: str + universal: bool = False + platform: str | None = None + + +def qt_static_configure_options() -> list[str]: + return ['-release', '-opensource', '-confirm-license', '-accessibility', + '-no-gui', + '-no-openssl', + '-no-feature-sql', + '-qt-zlib', + '-nomake', 'examples', + '-nomake', 'tests', + '-static'] + qt_static_platform_configure_options() + + +def qt_static_platform_configure_options() -> list[str]: + if is_windows_platform(): + return ['-static-runtime', '-no-icu'] + if is_linux_platform(): + return ['-no-icu', '-no-glib', '-qt-zlib', '-qt-pcre', '-qt-doubleconversion'] + return [] + + +def get_qt_src_path(qt_build_base: Path) -> Path: + return qt_build_base / 'src' + + +def get_qt_build_path(qt_build_base: Path) -> Path: + return qt_build_base / 'build' + + +def get_qt_install_path(qt_build_base: Path) -> Path: + return qt_build_base / 'install' + + +def configure_qt(params: BuildParams, src: Path, build: Path, install: Path) -> None: + build.mkdir(parents=True, exist_ok=True) + configure = src / "configure" + cmd = [str(configure), "-prefix", str(install)] + qt_static_configure_options() + if params.platform: + cmd.extend(['-platform', params.platform]) + if params.universal: + cmd.extend(['--', '-DCMAKE_OSX_ARCHITECTURES=x86_64;arm64']) + check_print_call(cmd, cwd=build) + + +def build_qt(params: BuildParams, build: Path) -> None: + check_print_call([params.make_command], cwd=build) + + +def install_qt(params: BuildParams, build: Path) -> None: + check_print_call([params.make_command, 'install'], cwd=build) + + +def build_sdktool_impl(params: BuildParams, qt_install_path: Path) -> None: + params.build_path.mkdir(parents=True, exist_ok=True) + cmake_args = [ + 'cmake', '-DCMAKE_PREFIX_PATH=' + str(qt_install_path), '-DCMAKE_BUILD_TYPE=Release' + ] + # force MSVC on Windows, because it looks for GCC in the PATH first, + # even if MSVC is first mentioned in the PATH... + # TODO would be nicer if we only did this if cl.exe is indeed first in the PATH + if is_windows_platform(): + cmake_args += ['-DCMAKE_C_COMPILER=cl', '-DCMAKE_CXX_COMPILER=cl'] + cmake_args += ['-DCMAKE_MSVC_RUNTIME_LIBRARY=MultiThreaded'] + + if params.universal: + cmake_args += ['-DCMAKE_OSX_ARCHITECTURES=x86_64;arm64'] + + cmd = cmake_args + ['-G', 'Ninja', str(params.src_path)] + check_print_call(cmd, cwd=params.build_path) + check_print_call(['cmake', '--build', '.'], cwd=params.build_path) + cmd = ['cmake', '--install', '.', '--prefix', str(params.target_path)] + check_print_call(cmd, cwd=params.build_path) + + +def sign_sdktool(params: BuildParams, + environment: dict[str, str]) -> None: + signing_identity = environment.get('SIGNING_IDENTITY') + if not is_mac_platform() or not signing_identity: + return + check_print_call(['codesign', '-o', 'runtime', '--force', '-s', signing_identity, + '-v', 'sdktool'], + cwd=params.target_path, + env=environment) + + +def get_single_subdir(path: Path): + entries = list(islice(path.iterdir(), 2)) + if len(entries) == 1: + return path / entries[0] + return path + + +def build_sdktool( + qt_src_url: str, + qt_build_base: Path, + sdktool_src_path: Path, + sdktool_build_path: Path, + sdktool_target_path: Path, + make_command: str, + universal: bool = False, + platform: str | None = None, + environment: dict[str, str] | None = None +) -> None: + if not environment: + environment = os.environ.copy() + params = BuildParams( + src_path=sdktool_src_path, + build_path=sdktool_build_path, + target_path=sdktool_target_path, + make_command=make_command, + platform=platform, + universal=universal + ) + qt_src = get_qt_src_path(qt_build_base) + qt_build = get_qt_build_path(qt_build_base) + qt_install = get_qt_install_path(qt_build_base) + download_and_extract([qt_src_url], qt_src, qt_build_base) + qt_src = get_single_subdir(qt_src) + configure_qt(params, qt_src, qt_build, qt_install) + build_qt(params, qt_build) + install_qt(params, qt_build) + build_sdktool_impl(params, qt_install) + sign_sdktool(params, environment) + + +def zip_sdktool( + sdktool_target_path: Path, out_7zip: Path +) -> None: + glob = "*.exe" if is_windows_platform() else "*" + check_print_call( + cmd=["7z", "a", str(out_7zip), glob], + cwd=sdktool_target_path + ) + + +def get_arguments() -> argparse.Namespace: + parser = argparse.ArgumentParser(description='Build sdktool') + parser.add_argument('--qt-url', help='URL to Qt sources', required=True) + parser.add_argument( + '--qt-build', help='Path that is used for building Qt', required=True, type=Path + ) + parser.add_argument('--src', help='Path to sdktool sources', required=True, type=Path) + parser.add_argument( + '--build', help='Path that is used for building sdktool', required=True, type=Path + ) + parser.add_argument( + '--install', help='Path that is used for installing sdktool', required=True, type=Path + ) + parser.add_argument('--make-command', help='Make command to use for Qt', required=True) + parser.add_argument('--platform', help='Platform argument for configuring Qt', + required=False) + parser.add_argument('--universal', help='Build universal binaries on macOS', + action='store_true', default=False, required=False) + return parser.parse_args() + + +def main() -> None: + args = get_arguments() + build_sdktool( + qt_src_url=args.qt_url, + qt_build_base=args.qt_build, + sdktool_src_path=args.src, + sdktool_build_path=args.build, + sdktool_target_path=args.install, + make_command=args.make_command, + platform=args.platform + ) + + +if __name__ == '__main__': + main() diff --git a/scripts/common.py b/scripts/common.py index 081469963f1..4ee0ddf7036 100644 --- a/scripts/common.py +++ b/scripts/common.py @@ -1,12 +1,17 @@ # Copyright (C) 2016 The Qt Company Ltd. # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 +from __future__ import annotations import argparse +import asyncio import os import locale +from pathlib import Path import shutil import subprocess import sys +from urllib.parse import urlparse +import urllib.request encoding = locale.getdefaultlocale()[1] if not encoding: @@ -27,13 +32,13 @@ def to_posix_path(path): return path.replace('\\', '/') return path -def check_print_call(command, workdir=None, env=None): +def check_print_call(command, cwd=None, env=None): print('------------------------------------------') print('COMMAND:') print(' '.join(['"' + c.replace('"', '\\"') + '"' for c in command])) - print('PWD: "' + (workdir if workdir else os.getcwd()) + '"') + print('PWD: "' + (str(cwd) if cwd else os.getcwd()) + '"') print('------------------------------------------') - subprocess.check_call(command, cwd=workdir, env=env) + subprocess.check_call(command, cwd=cwd, shell=is_windows_platform(), env=env) def get_git_SHA(path): @@ -105,6 +110,57 @@ def copytree(src, dst, symlinks=False, ignore=None): if errors: raise shutil.Error(errors) + +def extract_file(archive: Path, target: Path) -> None: + cmd_args = [] + if archive.suffix == '.tar': + cmd_args = ['tar', '-xf', str(archive)] + elif archive.suffixes[-2:] == ['.tar', '.gz'] or archive.suffix == '.tgz': + cmd_args = ['tar', '-xzf', str(archive)] + elif archive.suffixes[-2:] == ['.tar', '.xz']: + cmd_args = ['tar', '-xf', str(archive)] + elif archive.suffixes[-2:] == ['.tar', '.bz2'] or archive.suffix == '.tbz': + cmd_args = ['tar', '-xjf', str(archive)] + elif archive.suffix in ('.7z', '.zip', '.gz', '.xz', '.bz2', '.qbsp'): + cmd_args = ['7z', 'x', str(archive)] + else: + raise( + "Extract fail: %s. Not an archive or appropriate extractor was not found", str(archive) + ) + return + target.mkdir(parents=True, exist_ok=True) + subprocess.check_call(cmd_args, cwd=target) + + +async def download(url: str, target: Path) -> None: + print('- Starting download {} -> {}'.format(url, str(target))) + # Since urlretrieve does blocking I/O it would prevent parallel downloads. + # Run in default thread pool. + loop = asyncio.get_running_loop() + await loop.run_in_executor(None, urllib.request.urlretrieve, url, str(target)) + print('+ finished downloading {}'.format(str(target))) + + +def download_and_extract(urls: list[str], target: Path, temp: Path) -> None: + temp.mkdir(parents=True, exist_ok=True) + target_files = [] + # TODO make this work with file URLs, which then aren't downloaded + # but just extracted + async def impl(): + tasks : list[asyncio.Task] = [] + for url in urls: + u = urlparse(url) + filename = Path(u.path).name + target_file = temp / filename + target_files.append(target_file) + tasks.append(asyncio.create_task(download(url, target_file))) + for task in tasks: + await task + asyncio.run(impl()) + for file in target_files: + extract_file(file, target) + + def get_qt_install_info(qmake_bin): output = subprocess.check_output([qmake_bin, '-query']) decoded_output = output.decode(encoding) if encoding else output diff --git a/scripts/install_qt.py b/scripts/install_qt.py new file mode 100755 index 00000000000..c2c68f0e2ad --- /dev/null +++ b/scripts/install_qt.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# Copyright (C) 2024 The Qt Company Ltd. +# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 + +from __future__ import annotations +import argparse +from common import download_and_extract +from pathlib import Path +import subprocess +import sys +from tempfile import TemporaryDirectory +from typing import Optional + + +def get_arguments() -> argparse.Namespace: + parser = argparse.ArgumentParser(description='Install Qt from individual module archives') + parser.add_argument('--qt-path', help='path to Qt', type=Path, required=True) + parser.add_argument('--qt-module', help='Qt module package url (.7z) needed for building', + action='append', dest='qt_modules', default=[]) + + parser.add_argument('--base-url', help='Base URL for given module_name(s)') + parser.add_argument( + 'module_name', + help='Name of Qt module to install, based on --base-url and --base-url-postfix', + nargs='*' + ) + parser.add_argument( + '--base-url-postfix', + help='Postfix to add to URLs constructed from --base-url and given module_name(s)', + default='' + ) + + # Linux + parser.add_argument('--icu7z', help='a file or url where to get ICU libs as 7z') + + args = parser.parse_args(sys.argv[1:]) + + return args + + +def patch_qt(qt_path: Path) -> None: + print("##### patch Qt #####") + qmake_binary = qt_path / 'bin' / 'qmake' + # write qt.conf + with (qt_path / 'bin' / 'qt.conf').open('w', encoding='utf-8') as qt_conf_file: + qt_conf_file.write('[Paths]\n') + qt_conf_file.write('Prefix=..\n') + subprocess.check_call([str(qmake_binary), '-query'], cwd=qt_path) + + +def install_qt( + qt_path: Path, + qt_modules: list[str], + icu_url: Optional[str] = None +) -> None: + """ + Install Qt to directory qt_path with the specified module and library packages. + + Args: + qt_path: File system path to Qt (target install directory) + qt_modules: List of Qt module package URLs (.7z) + icu_url: Local or remote URI to Linux ICU libraries (.7z) + temp_path: Temporary path used for saving downloaded archives + + Raises: + SystemExit: When qt_modules list is empty + + """ + if not qt_modules: + raise SystemExit("No modules specified in qt_modules") + qt_path = qt_path.resolve() + need_to_install_qt = not qt_path.exists() + + with TemporaryDirectory() as temporary_dir: + if need_to_install_qt: + urls = qt_modules + if icu_url: + qt_modules.append(icu_url) + download_and_extract(urls, qt_path, Path(temporary_dir)) + patch_qt(qt_path) + + +def main() -> None: + """Main""" + args: argparse.Namespace = get_arguments() + # Check that qt_module(s) or base-url/module_name(s) combo is specified + if not args.qt_modules and not (args.base_url and args.module_name): + raise SystemExit("'qt-module(s)' and/or 'base-url' with 'module_name(s)' required") + # Create the list of modules from qt_modules + module_names with base_url and postfix + qt_modules: list[str] = args.qt_modules + if args.base_url and args.module_name: + for module in args.module_name: + qt_modules += [args.base_url + "/" + module + "/" + module + args.base_url_postfix] + + install_qt( + qt_path=args.qt_path, + qt_modules=qt_modules, + icu_url=args.icu7z + ) + + +if __name__ == '__main__': + main()