Merge pull request #249 from miurahr/topic-drop-cuteci

Drop feature to install old versions
This commit is contained in:
Hiroshi Miura
2021-05-24 23:21:49 +09:00
committed by GitHub
7 changed files with 4 additions and 519 deletions

View File

@@ -9,7 +9,6 @@ All notable changes to this project will be documented in this file.
Added Added
----- -----
* Support for installation of old versions(#236, #239)
* Add -c/--config option to specify custom settings.ini(#246) * Add -c/--config option to specify custom settings.ini(#246)
* Document for settings.ini configuration parameters(#246) * Document for settings.ini configuration parameters(#246)

View File

@@ -7,7 +7,6 @@ include pyproject.toml
include tox.ini include tox.ini
recursive-include aqt *.ini recursive-include aqt *.ini
recursive-include aqt *.json recursive-include aqt *.json
recursive-include aqt *.qs
recursive-include aqt *.yml recursive-include aqt *.yml
recursive-include ci *.7z recursive-include ci *.7z
recursive-include ci *.ini recursive-include ci *.ini

View File

@@ -1,256 +0,0 @@
#!/usr/bin/env python
#
# Copyright (C) 2021 Hiroshi Miura <miurahr@linux.com>
#
# 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
# the Software without restriction, including without limitation the rights to
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
# the Software, and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
# FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
# COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#
# --------------------------------------------------------------------------------
# Original license of component
# MIT License
#
# Copyright (c) 2019 Adrien Gavignet
#
# 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 the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
# --------------------------------------------------------------------------------
import hashlib
import os
import re
import shutil
import stat
import subprocess
import sys
from logging import getLogger
import requests
from requests.adapters import HTTPAdapter
from urllib3 import Retry
from aqt.exceptions import ArchiveDownloadError
from aqt.helper import altlink, downloadBinaryFile, getUrl
WORKING_DIR = os.getcwd()
CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
DEFAULT_INSTALL_SCRIPT = os.path.join(CURRENT_DIR, "install-qt.qs")
BLOCKSIZE = 1048576
class DeployCuteCI:
"""
Class in charge of Qt deployment
"""
def __init__(self, version, os_name, base, timeout):
self.major_minor = version[: version.rfind(".")]
self.timeout = timeout
if os_name == "linux":
arch = "x64"
ext = "run"
elif os_name == "mac":
arch = "x64"
ext = "dmg"
else:
arch = "x86"
ext = "exe"
if self.major_minor in [
"5.11",
"5.10",
"5.8",
"5.7",
"5.6",
"5.5",
"5.4",
"5.3",
"5.2",
]:
folder = "new_archive"
else:
folder = "archive"
self.installer_url = "{0}/{1}/qt/{2}/{3}/qt-opensource-{4}-{5}-{6}.{7}".format(
base, folder, self.major_minor, version, os_name, arch, version, ext
)
self.md5sums_url = (
self.installer_url[: self.installer_url.rfind("/")] + "/" + "md5sums.txt"
)
def _get_version(self, path):
# qt-opensource-windows-x86-5.12.2.exe
# qt-opensource-mac-x64-5.12.2.dmg
# qt-opensource-linux-x64-5.12.2.run
basename = os.path.basename(path)
res = re.search(r"-(\d+\.\d+.\d+)\.", basename)
if res is None:
raise Exception(
"Cannot get version from `{}` filename (expects name like: `qt-opensource-linux-x64-5.12.2.run`)".format(
basename
)
)
res.group(1)
return res.group(1)
def get_archive_name(self):
return self.installer_url[self.installer_url.rfind("/") + 1 :]
def _get_md5(self, archive, timeout):
expected_md5 = None
r_text = getUrl(self.md5sums_url, timeout, self.logger)
for line in r_text.split("\n"):
rec = line.split(" ")
if archive in rec:
expected_md5 = rec[0]
return expected_md5
def check_archive(self):
archive = self.get_archive_name()
if os.path.exists(archive):
timeout = (3.5, 3.5)
expected_md5 = self._get_md5(archive, timeout)
if expected_md5 is not None:
checksum = hashlib.md5()
with open(archive, "rb") as f:
data = f.read(BLOCKSIZE)
while len(data) > 0:
checksum.update(data)
data = f.read(BLOCKSIZE)
if (
checksum.hexdigest() == expected_md5
and os.stat(archive).st_mode & stat.S_IEXEC
):
return True
return False
def download_installer(self, response_timeout: int = 30):
"""
Download Qt if possible, also verify checksums.
:raises Exception: in case of failure
"""
logger = getLogger("aqt")
url = self.installer_url
archive = self.get_archive_name()
timeout = (3.5, response_timeout)
logger.info("Download Qt %s", url)
#
expected_md5 = self._get_md5(archive, timeout)
downloadBinaryFile(url, archive, "md5", expected_md5, timeout, logger)
with requests.Session() as session:
retry = Retry(connect=5, backoff_factor=0.5)
adapter = HTTPAdapter(max_retries=retry)
session.mount("http://", adapter)
session.mount("https://", adapter)
try:
r = session.get(
url, allow_redirects=False, stream=True, timeout=timeout
)
if r.status_code == 302:
newurl = altlink(r.url, r.headers["Location"], logger=logger)
logger.info("Redirected URL: {}".format(newurl))
r = session.get(newurl, stream=True, timeout=timeout)
except requests.exceptions.ConnectionError as e:
logger.error("Connection error: %s" % e.args)
raise e
except requests.exceptions.Timeout as e:
logger.error("Connection timeout: %s" % e.args)
raise e
else:
checksum = hashlib.md5()
try:
with open(archive, "wb") as fd:
for chunk in r.iter_content(chunk_size=8196):
fd.write(chunk)
checksum.update(chunk)
if expected_md5 is not None:
if checksum.hexdigest() != expected_md5:
raise ArchiveDownloadError(
"Download file is corrupted! Check sum error."
)
except Exception as e:
exc = sys.exc_info()
logger.error("Download error: %s" % exc[1])
raise e
os.chmod(archive, os.stat(archive).st_mode | stat.S_IEXEC)
return archive
def run_installer(self, archive, packages, destdir, keep_tools):
"""
Install Qt.
:param list: packages to install
:param str destdir: install directory
:param keep_tools: if True, keep Qt Tools after installation
:param bool verbose: enable verbosity
:raises Exception: in case of failure
"""
logger = getLogger("aqt")
env = os.environ.copy()
env["PACKAGES"] = ",".join(packages)
env["DESTDIR"] = destdir
install_script = os.path.join(CURRENT_DIR, "install-qt.qs")
installer_path = os.path.join(WORKING_DIR, archive)
cmd = [installer_path, "--script", install_script, "--verbose"]
if self.major_minor in ["5.11", "5.10"]:
cmd.extend(["--platform", "minimal"])
logger.info("Running installer %s", cmd)
try:
subprocess.run(cmd, timeout=self.timeout, env=env, check=True)
except subprocess.CalledProcessError as cpe:
if cpe.returncode == 3:
pass
logger.error("Installer error: %d" % cpe.returncode)
if cpe.stdout is not None:
logger.error(cpe.stdout)
if cpe.stderr is not None:
logger.error(cpe.stderr)
raise cpe
except subprocess.TimeoutExpired as te:
logger.error("Installer timeout expired: {}" % self.timeout)
if te.stdout is not None:
logger.error(te.stdout)
if te.stderr is not None:
logger.error(te.stderr)
raise te
if not keep_tools:
logger.info("Cleaning destdir")
files = os.listdir(destdir)
for name in files:
fullpath = os.path.join(destdir, name)
if re.match(r"\d+\.\d+.\d+", name):
# Qt stands in X.Y.Z dir, skip it
logger.info("Keep %s", fullpath)
continue
if os.path.isdir(fullpath):
shutil.rmtree(fullpath)
else:
os.remove(fullpath)
logger.info("Remove %s", fullpath)

View File

@@ -1,136 +0,0 @@
function debugObject(object) {
var lines = [];
for (var i in object) {
lines.push([i, object[i]].join(" "));
}
console.log(lines.join(","));
}
function Controller() {
// Not empty dir is no problem.
installer.setMessageBoxAutomaticAnswer("OverwriteTargetDirectory", QMessageBox.Yes);
// If Qt is already installed in dir, acknowlegde the error but situation is stuck.
installer.setMessageBoxAutomaticAnswer("TargetDirectoryInUse", QMessageBox.Ok);
// Allow to quit the installer when listing packages.
installer.setMessageBoxAutomaticAnswer("cancelInstallation", QMessageBox.Yes);
}
Controller.prototype.WelcomePageCallback = function() {
console.log("Welcome Page");
var widget = gui.currentPageWidget();
widget.completeChanged.connect(function() {
// For some reason, this page needs some delay.
gui.clickButton(buttons.NextButton);
});
}
Controller.prototype.CredentialsPageCallback = function() {
console.log("Credentials Page");
var login = installer.environmentVariable("QTLOGIN");
var password = installer.environmentVariable("QTPASSWORD");
if (login === "" || password === "") {
gui.clickButton(buttons.NextButton);
}
var widget = gui.currentPageWidget();
widget.loginWidget.EmailLineEdit.setText(login);
widget.loginWidget.PasswordLineEdit.setText(password);
gui.clickButton(buttons.NextButton);
}
Controller.prototype.IntroductionPageCallback = function() {
console.log("Introduction Page");
gui.clickButton(buttons.NextButton);
}
Controller.prototype.TargetDirectoryPageCallback = function() {
console.log("Target Directory Page");
var installDir = installer.environmentVariable("DESTDIR");
if (installDir) {
// If not present we assume we want to list packages.
var widget = gui.currentPageWidget();
widget.TargetDirectoryLineEdit.setText(installDir);
}
gui.clickButton(buttons.NextButton);
}
Controller.prototype.ComponentSelectionPageCallback = function() {
console.log("Component Selection Page");
var components = installer.components();
console.log("Available packages: " + components.length);
var packages = ["===LIST OF PACKAGES==="];
for (var i = 0 ; i < components.length ;i++) {
packages.push(components[i].name + " " + components[i].displayName);
}
packages.push("===END OF PACKAGES===");
console.log(packages.join("\n"));
if (installer.environmentVariable("LIST_PACKAGE_ONLY")) {
// Early exit
gui.clickButton(buttons.CancelButton);
return;
}
wantedPackages = installer.environmentVariable("PACKAGES").split(",");
console.log("Trying to install ", wantedPackages);
var widget = gui.currentPageWidget();
widget.deselectAll();
for (var i in wantedPackages) {
name = wantedPackages[i];
var found = false;
for (var j in components) {
if (components[j].name === name) {
found = true;
break;
}
}
if (found) {
console.log("Select " + name);
widget.selectComponent(name);
} else {
console.log("Package " + name + " not found");
}
}
widget.deselectComponent("qt.tools.qtcreator");
widget.deselectComponent("qt.tools.doc");
widget.deselectComponent("qt.tools.examples");
gui.clickButton(buttons.NextButton);
}
Controller.prototype.LicenseAgreementPageCallback = function() {
console.log("Accept License Agreement Page");
var widget = gui.currentPageWidget();
widget.AcceptLicenseRadioButton.setChecked(true);
gui.clickButton(buttons.NextButton);
}
Controller.prototype.ReadyForInstallationPageCallback = function() {
console.log("Ready For Installation Page");
gui.clickButton(buttons.CommitButton);
}
Controller.prototype.PerformInstallationPageCallback = function() {
console.log("Perform Installation Page");
installer.installationFinished.connect(function() {
console.log("Installation finished");
gui.clickButton(buttons.NextButton);
});
}
Controller.prototype.FinishedPageCallback = function() {
console.log("Finished Page");
var widget = gui.currentPageWidget();
if (widget.LaunchQtCreatorCheckBoxForm) {
widget.LaunchQtCreatorCheckBoxForm.launchQtCreatorCheckBox.setChecked(false);
} else if (widget.RunItCheckBox) {
widget.RunItCheckBox.setChecked(false);
}
gui.clickButton(buttons.FinishButton);
}

View File

@@ -33,13 +33,11 @@ import subprocess
import time import time
from logging import getLogger from logging import getLogger
import appdirs
from packaging.version import Version, parse from packaging.version import Version, parse
from texttable import Texttable from texttable import Texttable
import aqt import aqt
from aqt.archives import PackagesList, QtArchives, SrcDocExamplesArchives, ToolArchives from aqt.archives import PackagesList, QtArchives, SrcDocExamplesArchives, ToolArchives
from aqt.cuteci import DeployCuteCI
from aqt.exceptions import ( from aqt.exceptions import (
ArchiveConnectionError, ArchiveConnectionError,
ArchiveDownloadError, ArchiveDownloadError,
@@ -298,48 +296,6 @@ class Cli:
) )
) )
def run_offline_installer(self, args):
"""Run online_installer subcommand"""
start_time = time.perf_counter()
self.show_aqt_version()
os_name = args.host
qt_version = args.qt_version
arch = args.arch
output_dir = args.outputdir
if output_dir is None:
base_dir = os.getcwd()
else:
base_dir = os.path.realpath(output_dir)
if args.timeout is not None:
timeout = args.timeout
else:
timeout = 300
if args.base is not None:
base = args.base
else:
base = self.settings.baseurl
qt_ver_num = qt_version.replace(".", "")
packages = ["qt.qt5.{}.{}".format(qt_ver_num, arch)]
if args.archives is not None:
packages.extend(args.archives)
#
qa = os.path.join(appdirs.user_data_dir("Qt", None), "qtaccount.ini")
if not os.path.exists(qa):
self.logger.warning("Cannot find {}".format(qa))
cuteci = DeployCuteCI(qt_version, os_name, base, timeout)
if not cuteci.check_archive():
archive = cuteci.download_installer()
else:
self.logger.info("Reuse existent installer archive.")
archive = cuteci.get_archive_name()
cuteci.run_installer(archive, packages, base_dir, True)
self.logger.info("Finished installation")
self.logger.info(
"Time elapsed: {time:.8f} second".format(
time=time.perf_counter() - start_time
)
)
def _run_src_doc_examples(self, flavor, args): def _run_src_doc_examples(self, flavor, args):
start_time = time.perf_counter() start_time = time.perf_counter()
self.show_aqt_version() self.show_aqt_version()
@@ -681,54 +637,6 @@ class Cli:
list_parser.set_defaults(func=self.run_list) list_parser.set_defaults(func=self.run_list)
self._set_common_argument(list_parser) self._set_common_argument(list_parser)
# #
old_install = subparsers.add_parser(
"offline_installer",
formatter_class=argparse.RawTextHelpFormatter,
description="Install Qt using offiline installer. It requires downloading installer binary(500-1500MB).\n"
"Please help you for patience to wait downloding."
"It can accept environment variables:\n"
" QTLOGIN: qt account login name\n"
" QTPASSWORD: qt account password\n",
)
old_install.add_argument(
"qt_version", help='Qt version in the format of "5.X.Y"'
)
old_install.add_argument(
"host", choices=["linux", "mac", "windows"], help="host os name"
)
old_install.add_argument(
"arch",
help="\ntarget linux/desktop: gcc_64"
"\ntarget mac/desktop: clang_64"
"\nwindows/desktop: win64_msvc2017_64, win32_msvc2017"
"\n win64_msvc2015_64, win32_msvc2015",
)
old_install.add_argument(
"--archives",
nargs="*",
help="Specify packages to install.",
)
old_install.add_argument(
"-O",
"--outputdir",
nargs="?",
help="Target output directory(default current directory)",
)
old_install.add_argument(
"-b",
"--base",
nargs="?",
help="Specify mirror base url such as http://mirrors.ocf.berkeley.edu/qt/, "
"where 'online' folder exist.",
)
old_install.add_argument(
"--timeout",
nargs="?",
type=float,
help="Specify timeout for offline installer processing.(default: 300 sec)",
)
old_install.set_defaults(func=self.run_offline_installer)
#
help_parser = subparsers.add_parser("help") help_parser = subparsers.add_parser("help")
help_parser.set_defaults(func=self.show_help) help_parser.set_defaults(func=self.show_help)
parser.set_defaults(func=self.show_help) parser.set_defaults(func=self.show_help)

View File

@@ -121,29 +121,6 @@ Tool installation commands
You may need to looking for version number at https://download.qt.io/online/qtsdkrepository/ You may need to looking for version number at https://download.qt.io/online/qtsdkrepository/
Experimental commands
---------------------
.. program:: aqt
.. option:: offline_installer <Qt version> <target OS> <target architecture> --archives [<package>, ...]
[Experimental, Advanced] install Qt library specified version and target using offline installer.
When specify old versions that has already become end-of-life, aqt download
the installer from a proper server repository. A command intend to support version from 5.2 to 5.11.
User may need to set environment variable QTLOGIN and QTPASSWORD properly or
place qtaccount.ini file at proper place.
User should specify proper package names. Otherwise it may install default
packages.
A feature is considered as very experimental.
.. option:: --archives <list of archives>
archive packages to install. Expected values will be shown on log message.
Command examples Command examples
================ ================
@@ -207,9 +184,3 @@ Example: Show help message
.. code-block:: bash .. code-block:: bash
aqt help aqt help
Example: install old version
.. code-block:: bash
aqt offline_installer 5.11.2 linux gcc_64

View File

@@ -5,7 +5,7 @@ def test_cli_help(capsys):
expected = "".join( expected = "".join(
[ [
"usage: aqt [-h] [-c CONFIG] [--logging-conf LOGGING_CONF] [--logger LOGGER]\n", "usage: aqt [-h] [-c CONFIG] [--logging-conf LOGGING_CONF] [--logger LOGGER]\n",
" {install,doc,examples,src,tool,list,offline_installer,help} ...\n", " {install,doc,examples,src,tool,list,help} ...\n",
"\n", "\n",
"Installer for Qt SDK.\n", "Installer for Qt SDK.\n",
"\n", "\n",
@@ -20,7 +20,7 @@ def test_cli_help(capsys):
"subcommands:\n", "subcommands:\n",
" Valid subcommands\n", " Valid subcommands\n",
"\n", "\n",
" {install,doc,examples,src,tool,list,offline_installer,help}\n", " {install,doc,examples,src,tool,list,help}\n",
" subcommand for aqt Qt installer\n", " subcommand for aqt Qt installer\n",
] ]
) )
@@ -68,7 +68,7 @@ def test_cli_launch_with_no_argument(capsys):
expected = "".join( expected = "".join(
[ [
"usage: aqt [-h] [-c CONFIG] [--logging-conf LOGGING_CONF] [--logger LOGGER]\n", "usage: aqt [-h] [-c CONFIG] [--logging-conf LOGGING_CONF] [--logger LOGGER]\n",
" {install,doc,examples,src,tool,list,offline_installer,help} ...\n", " {install,doc,examples,src,tool,list,help} ...\n",
"\n", "\n",
"Installer for Qt SDK.\n", "Installer for Qt SDK.\n",
"\n", "\n",
@@ -83,7 +83,7 @@ def test_cli_launch_with_no_argument(capsys):
"subcommands:\n", "subcommands:\n",
" Valid subcommands\n", " Valid subcommands\n",
"\n", "\n",
" {install,doc,examples,src,tool,list,offline_installer,help}\n", " {install,doc,examples,src,tool,list,help}\n",
" subcommand for aqt Qt installer\n", " subcommand for aqt Qt installer\n",
] ]
) )