Add support for commercial versions of Qt (#878)

* Add install-qt-commercial feature and tests

* Make the auto-answers parameters, fix linter issues

* Fork and execv instead of using subprocess

* Return to simpler process execution method version

* Fix test

* Move commercial installer into its own file

* Fix shadowing of symbol platform causing errors

* Adapt test_cli for argparse format changes on py 3.13+

* Fix some errors, monkeypatch install test

* Add --override super command

* Properly handle --override and grab all the remaining commands when no quotes are given

* Fix tests

* Add base for modules, some niche features are not yet entirely implemented, and there are no updates to the testsuite

* Fix some mistakes

* Fix errors made with the monkeypatch, update Settings to make sure its init

* Tests commercial (#20)

* Full support of installation of all modules and addons
* Add auto setup of cache folder for each OS, add unattended parameter
* Fix settings folders
* Add graceful error message for overwrite case

* Fix windows issue

* Hidden summon works

* Remove both subprocess direct calls

* Dipose of temp folder

* Fix path issue

* Add list-qt-commercial command

* Fix help info

* Make no params valid for list-qt-commercial

* Fix lint errors, and param overflow when no args are passed to list

* Fix search

* Add tests for coverage, fix lint

* Test for overwriting, and for cache usage coverage

* Return to clean exec, ignoring CI fail to preserve code clarity

* Fix parsing of subprocess.run output for some python versions

* Make output more readable in console for list-qt-commercial

* Forward email and password to list request for users without a qtaccount.ini

* Change default settings

* Fix lint errors

* Fix check error
This commit is contained in:
Alexandre Poumaroux
2025-01-28 12:51:53 +01:00
committed by GitHub
parent 7917b2d725
commit cbe159f38a
8 changed files with 933 additions and 54 deletions

View File

@@ -1,3 +1,4 @@
import platform
import re
import sys
from pathlib import Path
@@ -15,8 +16,9 @@ from aqt.metadata import MetadataFactory, SimpleSpec, Version
def expected_help(actual, prefix=None):
expected = (
"usage: aqt [-h] [-c CONFIG]\n"
" {install-qt,install-tool,install-doc,install-example,install-src,"
"list-qt,list-tool,list-doc,list-example,list-src,help,version}\n"
" {install-qt,install-tool,install-qt-commercial,install-doc,install-example,"
"install-src,"
"list-qt,list-qt-commercial,list-tool,list-doc,list-example,list-src,help,version}\n"
" ...\n"
"\n"
"Another unofficial Qt Installer.\n"
@@ -32,7 +34,8 @@ def expected_help(actual, prefix=None):
" install-* subcommands are commands that install components\n"
" list-* subcommands are commands that show available components\n"
"\n"
" {install-qt,install-tool,install-doc,install-example,install-src,list-qt,"
" {install-qt,install-tool,install-qt-commercial,install-doc,install-example,"
"install-src,list-qt,list-qt-commercial,"
"list-tool,list-doc,list-example,list-src,help,version}\n"
" Please refer to each help message by using '--help' "
"with each subcommand\n",
@@ -520,3 +523,29 @@ def test_get_autodesktop_dir_and_arch_non_android(
), "Expected autodesktop install message."
elif expect["instruct"]:
assert any("You can install" in line for line in err_lines), "Expected install instruction message."
@pytest.mark.parametrize(
"cmd, expected_arch, expected_err",
[
pytest.param(
"install-qt-commercial desktop {} 6.8.0",
{"windows": "win64_msvc2022_64", "linux": "linux_gcc_64", "mac": "clang_64"},
"No Qt account credentials found. Either provide --user and --password or",
),
],
)
def test_cli_login_qt_commercial(capsys, monkeypatch, cmd, expected_arch, expected_err):
"""Test commercial Qt installation command"""
# Detect current platform
current_platform = platform.system().lower()
arch = expected_arch[current_platform]
cmd = cmd.format(arch)
cli = Cli()
cli._setup_settings()
result = cli.run(cmd.split())
_, err = capsys.readouterr()
assert str(err).find(expected_err)
assert not result == 0

View File

@@ -1676,30 +1676,30 @@ def test_install_qt6_wasm_autodesktop(monkeypatch, capsys, version, str_version,
assert result == 0
# Check output format
out, err = capsys.readouterr()
sys.stdout.write(out)
sys.stderr.write(err)
# Check output format
out, err = capsys.readouterr()
sys.stdout.write(out)
sys.stderr.write(err)
# Use regex that works for all platforms
expected_pattern = re.compile(
r"^INFO : aqtinstall\(aqt\) v.*? on Python 3.*?\n"
r"INFO : You are installing the Qt6-WASM version of Qt\n"
r"(?:INFO : Found extension .*?\n)*"
r"(?:INFO : Downloading (?:qt[^\n]*|icu[^\n]*)\n"
r"Finished installation of .*?\.7z in \d+\.\d+\n)*"
r"(?:INFO : Patching (?:/tmp/[^/]+|[A-Za-z]:[\\/].*?)/6\.8\.0/wasm_singlethread/bin/(?:qmake|qtpaths)(?:6)?\n)*"
r"INFO : \n"
r"INFO : Autodesktop will now install linux desktop 6\.8\.0 linux_gcc_64 as required by Qt6-WASM\n"
r"INFO : aqtinstall\(aqt\) v.*? on Python 3.*?\n"
r"(?:INFO : Found extension .*?\n)*"
r"(?:INFO : Downloading (?:qt[^\n]*|icu[^\n]*)\n"
r"Finished installation of .*?\.7z in \d+\.\d+\n)*"
r"INFO : Finished installation\n"
r"INFO : Time elapsed: \d+\.\d+ second\n$"
)
# Use regex that works for all platforms
expected_pattern = re.compile(
r"^INFO : aqtinstall\(aqt\) v.*? on Python 3.*?\n"
r"INFO : You are installing the Qt6-WASM version of Qt\n"
r"(?:INFO : Found extension .*?\n)*"
r"(?:INFO : Downloading (?:qt[^\n]*|icu[^\n]*)\n"
r"Finished installation of .*?\.7z in \d+\.\d+\n)*"
r"(?:INFO : Patching (?:/tmp/[^/]+|[A-Za-z]:[\\/].*?)/6\.8\.0/wasm_singlethread/bin/(?:qmake|qtpaths)(?:6)?\n)*"
r"INFO : \n"
r"INFO : Autodesktop will now install linux desktop 6\.8\.0 linux_gcc_64 as required by Qt6-WASM\n"
r"INFO : aqtinstall\(aqt\) v.*? on Python 3.*?\n"
r"(?:INFO : Found extension .*?\n)*"
r"(?:INFO : Downloading (?:qt[^\n]*|icu[^\n]*)\n"
r"Finished installation of .*?\.7z in \d+\.\d+\n)*"
r"INFO : Finished installation\n"
r"INFO : Time elapsed: \d+\.\d+ second\n$"
)
assert expected_pattern.match(err)
assert expected_pattern.match(err)
@pytest.mark.parametrize(
@@ -2054,3 +2054,104 @@ def test_installer_passes_base_to_metadatafactory(
sys.stderr.write(err)
assert expect_out.match(err), err
class CompletedProcess:
def __init__(self, args, returncode):
self.args = args
self.returncode = returncode
self.stdout = None
self.stderr = None
@pytest.mark.enable_socket
@pytest.mark.parametrize(
"cmd, arch_dict, details, expected_command",
[
(
"install-qt-commercial desktop {} 6.8.0 " "--outputdir ./install-qt-commercial " "--user {} --password {}",
{"windows": "win64_msvc2022_64", "linux": "linux_gcc_64", "mac": "clang_64"},
["./install-qt-commercial", "qt6", "680"],
"qt-unified-{}-x64-online.run --email ******** --pw ******** --root {} "
"--accept-licenses --accept-obligations "
"--confirm-command "
"--auto-answer OperationDoesNotExistError=Ignore,OverwriteTargetDirectory=No,"
"stopProcessesForUpdates=Cancel,installationErrorWithCancel=Cancel,installationErrorWithIgnore=Ignore,"
"AssociateCommonFiletypes=Yes,telemetry-question=No install qt.{}.{}.{}",
),
(
"install-qt-commercial desktop {} 6.8.1 " "--outputdir ./install-qt-commercial " "--user {} --password {}",
{"windows": "win64_msvc2022_64", "linux": "linux_gcc_64", "mac": "clang_64"},
["./install-qt-commercial", "qt6", "681"],
"qt-unified-{}-x64-online.run --email ******** --pw ******** --root {} "
"--accept-licenses --accept-obligations "
"--confirm-command "
"--auto-answer OperationDoesNotExistError=Ignore,OverwriteTargetDirectory=Yes,"
"stopProcessesForUpdates=Cancel,installationErrorWithCancel=Cancel,installationErrorWithIgnore=Ignore,"
"AssociateCommonFiletypes=Yes,telemetry-question=No install qt.{}.{}.{}",
),
],
)
def test_install_qt_commercial(
capsys, monkeypatch, cmd: str, arch_dict: dict[str, str], details: list[str], expected_command: str
) -> None:
"""Test commercial Qt installation command"""
# Mock subprocess.run instead of run_static_subprocess_dynamically
def mock_subprocess_run(*args, **kwargs):
# This will be called instead of the real subprocess.run
return CompletedProcess(args=args[0], returncode=0)
# Patch subprocess.run directly
monkeypatch.setattr("subprocess.run", mock_subprocess_run)
current_platform = sys.platform.lower()
arch = arch_dict[current_platform]
abs_out = Path(details[0]).absolute()
formatted_cmd = cmd.format(arch, "vofab76634@gholar.com", "WxK43TdWCTmxsrrpnsWbjPfPXVq3mtLK")
formatted_expected = expected_command.format(current_platform, abs_out, *details[1:], arch)
cli = Cli()
cli._setup_settings()
try:
cli.run(formatted_cmd.split())
except AttributeError:
out = " ".join(capsys.readouterr())
assert str(out).find(formatted_expected) >= 0
def modify_qt_config(content):
"""
Takes content of INI file as string and returns modified content
"""
lines = content.splitlines()
in_qt_commercial = False
modified = []
for line in lines:
# Check if we're entering qtcommercial section
if line.strip() == "[qtcommercial]":
in_qt_commercial = True
# If in qtcommercial section, look for the target line
if in_qt_commercial and "overwrite_target_directory : No" in line:
line = "overwrite_target_directory : Yes"
elif in_qt_commercial and "overwrite_target_directory : Yes" in line:
line = "overwrite_target_directory : No"
modified.append(line)
return "\n".join(modified)
script_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(script_dir, "../aqt/settings.ini")
with open(config_path, "r") as f:
content = f.read()
modified_content = modify_qt_config(content)
with open(config_path, "w") as f:
f.write(modified_content)

View File

@@ -1269,3 +1269,19 @@ def test_find_installed_qt_mingw_dir(expected_result: str, installed_files: List
actual_result = QtRepoProperty.find_installed_desktop_qt_dir(host, base_path, Version(qt_ver))
assert (actual_result.name if actual_result else None) == expected_result
# Test error cases
@pytest.mark.parametrize(
"args, expected_error",
[
(["list-qt-commercial", "--bad-flag"], "usage: aqt [-h] [-c CONFIG]"),
],
)
def test_list_qt_commercial_errors(capsys, args, expected_error):
"""Test error handling in list-qt-commercial command"""
cli = Cli()
with pytest.raises(SystemExit):
cli.run(args)
_, err = capsys.readouterr()
assert expected_error in err