diff --git a/aqt/helper.py b/aqt/helper.py index c89b013f..9cdc7d95 100644 --- a/aqt/helper.py +++ b/aqt/helper.py @@ -4,8 +4,6 @@ import logging import multiprocessing import os -import pathlib -import subprocess import sys import xml.etree.ElementTree as ElementTree from typing import List, Optional @@ -58,64 +56,6 @@ def altlink(url: str, alt: str, logger=None): return next(filter(lambda mirror: not any(mirror.startswith(b) for b in blacklist), mirrors), alt) -def versiontuple(v: str): - return tuple(map(int, (v.split(".")))) - - -class Updater: - - def __init__(self, prefix: pathlib.Path, logger): - self.logger = logger - self.prefix = prefix - self.qmake_path = None - self.qconfigs = {} - self._detect_qmake(prefix) - - def _patch_qtcore(self): - framework_dir = self.prefix.joinpath("lib", "QtCore.framework") - assert framework_dir.exists(), "Invalid installation prefix" - for component in ["QtCore", "QtCore_debug"]: - if framework_dir.joinpath(component).exists(): - qtcore_path = framework_dir.joinpath(component).resolve() - self.logger.info("Patching {}".format(qtcore_path)) - self._patch_file(qtcore_path, bytes(str(self.prefix), "ascii")) - - def _patch_file(self, file: pathlib.Path, newpath: bytes): - PREFIX_VAR = b"qt_prfxpath=" - st = file.stat() - data = file.read_bytes() - idx = data.find(PREFIX_VAR) - if idx > 0: - return - assert len(newpath) < 256, "Qt Prefix path is too long(255)." - data = data[:idx] + PREFIX_VAR + newpath + data[idx + len(newpath):] - file.write_bytes(data) - os.chmod(str(file), st.st_mode) - - def _detect_qmake(self, prefix): - ''' detect Qt configurations from qmake - ''' - for qmake_path in [prefix.joinpath('bin', 'qmake'), prefix.joinpath('bin', 'qmake.exe')]: - if qmake_path.exists(): - result = subprocess.run([str(qmake_path), '-query'], stdout=subprocess.PIPE) - if result.returncode == 0: - self.qmake_path = qmake_path - for line in result.stdout.splitlines(): - vals = line.decode('UTF-8').split(':') - self.qconfigs[vals[0]] = vals[1] - break - - def patch_qt(self, target): - ''' patch works ''' - self.logger.info("Patching qmake") - mac_exceptions = ['ios', 'android', 'wasm_32', - 'android_x86_64', 'android_arm64_v8a', 'android_x86', 'android_armv7'] - if target.os_name == 'mac' and target.arch not in mac_exceptions: - self._patch_qtcore() - if self.qmake_path is not None: - self._patch_file(self.qmake_path, bytes(str(self.prefix), 'UTF-8')) - - class Settings(object): """Class to hold configuration and settings. Actual values are stored in 'settings.ini' file. diff --git a/aqt/installer.py b/aqt/installer.py index d8f1234b..3446f08d 100644 --- a/aqt/installer.py +++ b/aqt/installer.py @@ -44,7 +44,8 @@ from aqt.archives import (ArchiveConnectionError, ArchiveDownloadError, ArchiveListError, PackagesList, QtArchives, SrcDocExamplesArchives, ToolArchives) -from aqt.helper import Settings, Updater, altlink, versiontuple +from aqt.helper import Settings, altlink +from aqt.updater import Updater try: from importlib import metadata as importlib_metadata # noqa @@ -530,5 +531,4 @@ def finisher(target, base_dir, logger): raise e prefix = pathlib.Path(base_dir) / target.version / target.arch updater = Updater(prefix, logger) - if versiontuple(target.version) < (5, 14, 2): - updater.patch_qt(target) + updater.qtpatch(target) diff --git a/aqt/updater.py b/aqt/updater.py new file mode 100644 index 00000000..45f8063b --- /dev/null +++ b/aqt/updater.py @@ -0,0 +1,91 @@ +import os +import pathlib +import subprocess + + +class Updater: + + def __init__(self, prefix: pathlib.Path, logger): + self.logger = logger + self.prefix = prefix + self.qmake_path = None + self.qconfigs = {} + self._detect_qmake(prefix) + + def _patch_qtcore(self, lib_dir, components, encoding): + for component in components: + if lib_dir.joinpath(component).exists(): + qtcore_path = lib_dir.joinpath(component).resolve() + self.logger.info("Patching {}".format(qtcore_path)) + newpath = bytes(str(self.prefix), encoding) + self._patch_binfile(qtcore_path, b"qt_prfxpath=", newpath) + + def _patch_binfile(self, file: pathlib.Path, key: bytes, newpath: bytes): + """Patch binary file with key/value""" + st = file.stat() + data = file.read_bytes() + idx = data.find(key) + if idx > 0: + return + assert len(newpath) < 256, "Qt Prefix path is too long(255)." + data = data[:idx] + key + newpath + data[idx + len(newpath):] + file.write_bytes(data) + os.chmod(str(file), st.st_mode) + + def _patch_pkgconfig(self, file: pathlib.Path): + for pcfile in file.glob("*.pc"): + self.logger.info("Patching {}".format(pcfile)) + self._patch_textfile(pcfile, "prefix=/home/qt/work/install", 'prefix={}'.format(str(self.prefix))) + + def _patch_textfile(self, file: pathlib.Path, old: str, new: str): + st = file.stat() + data = file.read_text("UTF-8") + data = data.replace(old, new) + file.write_text(data, "UTF-8") + os.chmod(str(file), st.st_mode) + + def _detect_qmake(self, prefix): + """ detect Qt configurations from qmake + """ + for qmake_path in [prefix.joinpath('bin', 'qmake'), prefix.joinpath('bin', 'qmake.exe')]: + if not qmake_path.exists(): + return + try: + result = subprocess.run([str(qmake_path), '-query'], stdout=subprocess.PIPE) + except subprocess.SubprocessError: + return + else: + if result.returncode == 0: + self.qmake_path = qmake_path + for line in result.stdout.splitlines(): + vals = line.decode('UTF-8').split(':') + self.qconfigs[vals[0]] = vals[1] + + def _versiontuple(self, v: str): + return tuple(map(int, (v.split(".")))) + + def qtpatch(self, target): + """ patch works """ + if target.os_name == 'linux': + self.logger.info("Patching pkgconfig configurations") + self._patch_pkgconfig(self.prefix.joinpath("lib", "pkgconfig")) + + if target.arch not in ['ios', 'android', 'wasm_32', 'android_x86_64', 'android_arm64_v8a', 'android_x86', + 'android_armv7']: + if target.os_name == 'mac': + self.logger.info("Patching QtCore") + self._patch_qtcore(self.prefix.joinpath("lib", "QtCore.framework"), ["QtCore", "QtCore_debug"], "UTF-8") + elif target.os_name == 'linux': + self.logger.info("Patching libQt(5|6)Core") + self._patch_qtcore(self.prefix.joinpath("lib"), ["libQt5Core.so", "libQt6Core.so"], "UTF-8") + elif target.os_name == 'windows': + self.logger.info("Patching Qt(5|6)Core.dll") + self._patch_qtcore(self.prefix.joinpath("bin"), ["Qt5Cored.dll", "Qt5Core.dll", "Qt6Core.dll", + "Qt6Cored.dll"], "UTF-8") + else: + # no need to patch Qt5Core + pass + + if self.qmake_path is not None: + self.logger.info("Patching qmake") + self._patch_binfile(self.qmake_path, key=b"qt_prfxpath=", newpath=bytes(str(self.prefix), 'UTF-8')) diff --git a/ci/steps.yml b/ci/steps.yml index 329a01c1..840d0548 100644 --- a/ci/steps.yml +++ b/ci/steps.yml @@ -158,6 +158,9 @@ steps: } else { Write-Host '##vso[task.setvariable variable=VSVER]2015' } + cd $(Build.BinariesDirectory)\Qt\$(QT_VERSION)\$(ARCHDIR)\bin + Invoke-WebRequest -Uri https://download.qt.io/official_releases/jom/jom.zip -OutFile jom.zip + unzip jom.zip condition: eq( variables['Agent.OS'], 'Windows_NT') displayName: Detect toolchain for Windows and update PATH - script: | @@ -176,7 +179,7 @@ steps: 7z x $(Build.SourcesDirectory)\ci\helloworld.7z cd .. qmake $(Build.BinariesDirectory)\tests\helloworld - nmake + jom condition: and(eq( variables['Agent.OS'], 'Windows_NT'), eq(variables['TOOLCHAIN'], 'MSVC'), eq(variables['MODULE'], ''), ne(variables['VSVER'], '2019')) displayName: build test with qmake with MSVC w/o extra module - powershell: |