Patch qmake as finalize process (#130)

* Patch qmake hard coded path with installed prefix(#100)
* AP: Update tests
 - Test android target with 5.14.x
 - Test ios target on mac
* Does not patch mac framework when android, ios and wasm
* Update changelog
* GHA: Update to run qmake to check patch
  - Check qmake works well if patched.

Signed-off-by: Hiroshi Miura <miurahr@linux.com>
This commit is contained in:
Hiroshi Miura
2020-05-21 21:56:04 +09:00
committed by GitHub
parent f25cee40b2
commit 72c4589e7d
8 changed files with 149 additions and 35 deletions

View File

@@ -1,6 +1,6 @@
name: Test on GH actions environment name: Test on GH actions environment
on: [push, pull_request] on: push
jobs: jobs:
test: test:
@@ -9,7 +9,7 @@ jobs:
matrix: matrix:
os: [windows-latest, macOS-latest, ubuntu-latest] os: [windows-latest, macOS-latest, ubuntu-latest]
py: [3.6, 3.8] py: [3.6, 3.8]
qtver: [5.14.2, 5.15.0] qtver: [5.14.1, 5.15.0]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
with: with:
@@ -29,7 +29,6 @@ jobs:
python -m pip install ./ --user python -m pip install ./ --user
- name: Run Aqt - name: Run Aqt
run: | run: |
import os
import subprocess import subprocess
timeout = 300 timeout = 300
command_line = ["python", "-m", "aqt", "install"] command_line = ["python", "-m", "aqt", "install"]
@@ -46,7 +45,34 @@ jobs:
args = [qtver, "linux", "desktop", "gcc_64"] args = [qtver, "linux", "desktop", "gcc_64"]
command_line.extend(args) command_line.extend(args)
try: try:
subprocess.run(command_line, timeout=timeout, check=True) res = subprocess.run(command_line, timeout=timeout, check=True)
except subprocess.CalledProcessError as cpe: except subprocess.CalledProcessError as cpe:
exit(cpe.returncode) exit(cpe.returncode)
assert res.returncode == 0
shell: python
- name: Test qmake -query
run: |
import pathlib
from subprocess import CalledProcessError, PIPE, run
platform = "${{ matrix.os }}"
qtver = "${{ matrix.qtver }}"
if platform == "windows-latest":
if qtver.startswith('5.15'):
arch_dir = 'msvc2019_64'
else:
arch_dir = 'msvc2017_64'
elif platform == "macOS-latest":
arch_dir = 'clang_64'
else:
arch_dir = 'gcc_64'
try:
res = run([f"{qtver}/{arch_dir}/bin/qmake", "-query"], timeout=15, check=True, stdout=PIPE)
except CalledProcessError as cpe:
exit(cpe.returncode)
if res.returncode == 0:
qt_prefix_path = pathlib.Path.cwd() / qtver / arch_dir
for line in res.stdout.splitlines():
if line.startswith(b'QT_INSTALL_PREFIX'):
result = line[18:].decode('UTF-8')
assert qt_prefix_path.samefile(result)
shell: python shell: python

View File

@@ -17,6 +17,14 @@ Added
Changed Changed
------- -------
* Patch qmake when finishing installation.(#100)
qmake has a hard-coded prefix path, and aqt modify binary in finish phase.
it is not necessary for Qt 5.14.2, 5.15.0 and later.
This behavior try to be as same as a Qt installer framework doing.
* Patch Framework.QtCore when finishing installation.(#100)
As same as qmake, framework also has a hard-coded prefix path.
(Suggestions from @agateau)
Fixed Fixed
----- -----

View File

@@ -34,6 +34,14 @@ class ArchiveDownloadError(Exception):
pass pass
class TargetConfig:
def __init__(self, version, target, arch, os_name):
self.version = version
self.target = target
self.arch = arch
self.os_name = os_name
class QtPackage: class QtPackage:
""" """
Hold package information. Hold package information.
@@ -146,13 +154,13 @@ class QtArchives:
""" """
return self.archives return self.archives
def get_target_config(self): def get_target_config(self) -> TargetConfig:
"""Get target configuration """Get target configuration
:return: configured target and its version with arch :return: configured target and its version with arch
:rtype: tuple(version, target, arch) :rtype: TargetConfig object
""" """
return self.version, self.target, self.arch return TargetConfig(self.version, self.target, self.arch, self.os_name)
class ToolArchives(QtArchives): class ToolArchives(QtArchives):
@@ -198,9 +206,9 @@ class ToolArchives(QtArchives):
self.archives.append(QtPackage(name, package_url, archive, package_desc, self.archives.append(QtPackage(name, package_url, archive, package_desc,
has_mirror=(self.mirror is not None))) has_mirror=(self.mirror is not None)))
def get_target_config(self): def get_target_config(self) -> TargetConfig:
"""Get target configuration. """Get target configuration.
:return tuple of three parameter, "Tools", target and arch :return tuple of three parameter, "Tools", target and arch
""" """
return "Tools", self.target, self.arch return TargetConfig("Tools", self.target, self.arch, self.os_name)

View File

@@ -9,6 +9,7 @@
{"os_name": "mac", "target": "desktop", "arch": "clang_64"}, {"os_name": "mac", "target": "desktop", "arch": "clang_64"},
{"os_name": "mac", "target": "desktop", "arch": "wasm_32"}, {"os_name": "mac", "target": "desktop", "arch": "wasm_32"},
{"os_name": "mac", "target": "ios", "arch": "ios"}, {"os_name": "mac", "target": "ios", "arch": "ios"},
{"os_name": "mac", "target": "android", "arch": "android"},
{"os_name": "windows", "target": "desktop", "arch": "win64_msvc2019_64"}, {"os_name": "windows", "target": "desktop", "arch": "win64_msvc2019_64"},
{"os_name": "windows", "target": "desktop", "arch": "win32_msvc2019"}, {"os_name": "windows", "target": "desktop", "arch": "win32_msvc2019"},
{"os_name": "windows", "target": "desktop", "arch": "win64_msvc2017_64"}, {"os_name": "windows", "target": "desktop", "arch": "win64_msvc2017_64"},

View File

@@ -51,3 +51,7 @@ def altlink(url: str, alt: str, logger=None):
# Return first priority item which is not blacklist in mirrors list, # Return first priority item which is not blacklist in mirrors list,
# if not found then return alt in default # if not found then return alt in default
return next(filter(lambda mirror: not any(mirror.startswith(b) for b in blacklist), mirrors), alt) 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("."))))

View File

@@ -2,6 +2,7 @@
# #
# Copyright (C) 2018 Linus Jahn <lnj@kaidan.im> # Copyright (C) 2018 Linus Jahn <lnj@kaidan.im>
# Copyright (C) 2019,2020 Hiroshi Miura <miurahr@linux.com> # Copyright (C) 2019,2020 Hiroshi Miura <miurahr@linux.com>
# Copyright (C) 2020, Aurélien Gâteau
# #
# Permission is hereby granted, free of charge, to any person obtaining a copy of # Permission is hereby granted, free of charge, to any person obtaining a copy of
# this software and associated documentation files (the "Software"), to deal in # this software and associated documentation files (the "Software"), to deal in
@@ -22,6 +23,7 @@
import concurrent.futures import concurrent.futures
import os import os
import pathlib
import subprocess import subprocess
import sys import sys
import time import time
@@ -33,7 +35,8 @@ from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry from urllib3.util.retry import Retry
from aqt.archives import QtPackage from aqt.archives import QtPackage
from aqt.helper import altlink from aqt.helper import altlink, versiontuple
from aqt.qtpatch import Updater
from aqt.settings import Settings from aqt.settings import Settings
@@ -119,8 +122,20 @@ class QtInstaller:
self.logger.error(cpe.stderr) self.logger.error(cpe.stderr)
raise cpe raise cpe
def make_conf_files(self, qt_version, arch_dir): def get_arch_dir(self, arch):
if arch.startswith('win64_mingw'):
arch_dir = arch[6:] + '_64'
elif arch.startswith('win32_mingw'):
arch_dir = arch[6:] + '_32'
elif arch.startswith('win'):
arch_dir = arch[6:]
else:
arch_dir = arch
return arch_dir
def make_conf_files(self, qt_version, arch):
"""Make Qt configuration files, qt.conf and qtconfig.pri""" """Make Qt configuration files, qt.conf and qtconfig.pri"""
arch_dir = self.get_arch_dir(arch)
try: try:
# prepare qt.conf # prepare qt.conf
with open(os.path.join(self.base_dir, qt_version, arch_dir, 'bin', 'qt.conf'), 'w') as f: with open(os.path.join(self.base_dir, qt_version, arch_dir, 'bin', 'qt.conf'), 'w') as f:
@@ -148,17 +163,14 @@ class QtInstaller:
if len(not_done) > 0: if len(not_done) > 0:
self.logger.error("Installation error detected.") self.logger.error("Installation error detected.")
exit(1) exit(1)
# finalize # finalize
qt_version, target, arch = self.qt_archives.get_target_config() target = self.qt_archives.get_target_config()
if qt_version != "Tools": # tools installation if target.version == "Tools":
if arch.startswith('win64_mingw'): pass
arch_dir = arch[6:] + '_64' else:
elif arch.startswith('win32_mingw'): self.make_conf_files(target.version, target.arch)
arch_dir = arch[6:] + '_32' prefix = pathlib.Path(self.base_dir) / target.version / target.arch
elif arch.startswith('win'): updater = Updater(prefix, self.logger)
arch_dir = arch[6:] if versiontuple(target.version) < (5, 14, 2):
else: updater.patch_qt(target)
arch_dir = arch
self.make_conf_files(qt_version, arch_dir)
self.logger.info("Finished installation") self.logger.info("Finished installation")

57
aqt/qtpatch.py Normal file
View File

@@ -0,0 +1,57 @@
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):
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'))

View File

@@ -53,9 +53,12 @@ for qt_version in qt_versions:
BuildJob(qt_version, 'mac', 'desktop', 'clang_64', "clang_64") BuildJob(qt_version, 'mac', 'desktop', 'clang_64', "clang_64")
) )
# Mac iOS # Mac iOS, android
mac_build_jobs.append( mac_build_jobs.extend(
BuildJob('5.13.2', 'mac', 'ios', 'ios', 'ios') [
BuildJob('5.13.2', 'mac', 'ios', 'ios', 'ios'),
BuildJob('5.14.1', 'mac', 'android', 'android', 'android')
]
) )
# Windows Desktop # Windows Desktop
@@ -93,15 +96,10 @@ windows_build_jobs.append(
BuildJob('5.14.2', 'windows', 'desktop', 'wasm_32', "wasm_32") BuildJob('5.14.2', 'windows', 'desktop', 'wasm_32', "wasm_32")
) )
# Androids for Linux platforms # android
# aqt is for CI/CD systems! linux_build_jobs.append(
# Users might develop on Win/Mac, but are most likely to use Linux for CI/CD with BuildJob('5.14.1', 'linux', 'android', 'android', 'android')
# the Android ecosystem. )
for android_arch in ['android_x86_64', 'android_arm64_v8a', 'android_x86', 'android_armv7']:
linux_build_jobs.append(
BuildJob('5.13.2', 'linux', 'android', android_arch, android_arch)
)
matrices = {} matrices = {}