diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..be9256a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +.venv +build +dist +venv diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 565f182..2af14e6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -17,7 +17,8 @@ Added Changed ------- -* Install not only basic packages also optional pacakges. +* Install not only basic packages also optional packages. +* Rename project/command to aqt - Another QT installer Fixed ----- @@ -39,7 +40,7 @@ Security Changed ------- -* Support multiprocess concurent download and installation. +* Support multiprocess concurrent download and installation. `v0.0.2`_ (4, March, 2019) ======================== diff --git a/README.rst b/README.rst index d1358b9..57c5742 100644 --- a/README.rst +++ b/README.rst @@ -1,5 +1,5 @@ -Qt CLI installer -################ +Another Qt installer(aqt) +========================= .. |macos| image:: https://dev.azure.com/miurahr/github/_apis/build/status/miurahr.qli-installer?branchName=master&jobName=macOS :target: https://dev.azure.com/miurahr/github/_build/latest?definitionId=6&branchName=master diff --git a/aqt/__init__.py b/aqt/__init__.py new file mode 100644 index 0000000..ea2f4db --- /dev/null +++ b/aqt/__init__.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# +# Copyright (C) 2018 Linus Jahn +# Copyright (C) 2019 Hiroshi Miura +# +# 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 argparse +import sys + +from argparse import RawTextHelpFormatter +from aqt.archives import QtArchives +from aqt.installer import QtInstaller + + +def main(): + parser = argparse.ArgumentParser(description='Install Qt SDK.', formatter_class=RawTextHelpFormatter, add_help=True) + parser.add_argument("qt_version", help="Qt version in the format of \"5.X.Y\"") + parser.add_argument('host', choices=['linux', 'mac', 'windows'], help="host os name") + parser.add_argument('target', choices=['desktop', 'android', 'ios'], help="target sdk") + parser.add_argument('arch', nargs='?', help="\ntarget linux/desktop: gcc_64" + "\ntarget mac/desktop: clang_64" + "\ntarget mac/ios: ios" + "\nwindows/desktop: win64_msvc2017_64, win64_msvc2015_64" + "\n in32_msvc2015, win32_mingw53" + "\nandroid: android_x86, android_armv7") + args = parser.parse_args() + arch = args.arch + target = args.target + os_name = args.host + if arch is None: + if os_name == "linux" and target == "desktop": + arch = "gcc_64" + elif os_name == "mac" and target == "desktop": + arch = "clang_64" + elif os_name == "mac" and target == "ios": + arch = "ios" + if arch == "": + print("Please supply a target architecture.") + args.print_help() + exit(1) + qt_version = args.qt_version + + archives = QtArchives(os_name, qt_version, target, arch) + qt_installer = QtInstaller(archives) + qt_installer.install(qt_version, arch) + + sys.stdout.write("\033[K") + print("Finished installation") + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/aqt/archives.py b/aqt/archives.py new file mode 100644 index 0000000..29f9e8f --- /dev/null +++ b/aqt/archives.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python +# +# Copyright (C) 2018 Linus Jahn +# Copyright (C) 2019 Hiroshi Miura +# +# 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. + +from six.moves import urllib +import xml.etree.ElementTree as ElementTree + + +class QtPackage: + name = "" + url = "" + archive = "" + desc = "" + + def __init__(self, name, archive_url, archive, package_desc): + self.name = name + self.url = archive_url + self.archive = archive + self.desc = package_desc + + def get_name(self): + return self.name + + def get_url(self): + return self.url + + def get_archive(self): + return self.archive + + def get_desc(self): + return self.desc + + +class QtArchives: + BASE_URL = 'https://download.qt.io/online/qtsdkrepository/' + archives = [] + + def __init__(self, os_name, qt_version, target, arch): + qt_ver_num = qt_version.replace(".", "") + if os_name == 'windows': + archive_url = self.BASE_URL + os_name + '_x86/' + target + '/' + 'qt5_' + qt_ver_num + '/' + else: + archive_url = self.BASE_URL + os_name + '_x64/' + target + '/' + 'qt5_' + qt_ver_num + '/' + + # Get packages index + update_xml_url = "{0}Updates.xml".format(archive_url) + proxies = urllib.request.ProxyHandler({}) + opener = urllib.request.build_opener(proxies) + urllib.request.install_opener(opener) + content = urllib.request.urlopen(update_xml_url).read() + self.update_xml = ElementTree.fromstring(content) + for packageupdate in self.update_xml.iter("PackageUpdate"): + name = packageupdate.find("Name").text + if name.split(".")[-1] != arch: + continue + if name.split(".")[-2] == "debug_info": + continue + if packageupdate.find("DownloadableArchives").text is None: + continue + if name == "qt.qt5.{}.{}".format(qt_ver_num, arch) or name == "qt.{}.{}".format(qt_ver_num, arch): + # basic packages + pass + else: + # optional packages: FIXME: check option whether install or not + pass + downloadable_archives = packageupdate.find("DownloadableArchives").text.split(", ") + full_version = packageupdate.find("Version").text + package_desc = packageupdate.find("Description").text + for archive in downloadable_archives: + package_url = archive_url + name + "/" + full_version + archive + self.archives.append(QtPackage(name, package_url, archive, package_desc)) + + if len(self.archives) == 0: + print("Error while parsing package information!") + exit(1) + + def get_archives(self): + return self.archives + diff --git a/aqt/installer.py b/aqt/installer.py new file mode 100644 index 0000000..b0d5d18 --- /dev/null +++ b/aqt/installer.py @@ -0,0 +1,93 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 Linus Jahn +# Copyright (C) 2019 Hiroshi Miura +# +# 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 os +import platform +import subprocess +import sys +from six.moves import urllib + +from multiprocessing.dummy import Pool + +NUM_PROCESS = 3 + + +class QtInstaller: + def __init__(self, qt_archives): + self.qt_archives = qt_archives + + @staticmethod + def retrieve_archive(package): + archive = package.get_archive() + url = package.get_url() + sys.stdout.write("\033[K") + print("-Downloading {}...".format(url)) + proxies = urllib.request.ProxyHandler({}) + opener = urllib.request.build_opener(proxies) + urllib.request.install_opener(opener) + urllib.request.urlretrieve(url, archive) + sys.stdout.write("\033[K") + print("-Extracting {}...".format(archive)) + if platform.system() is 'Windows': + subprocess.run([r'C:\Program Files\7-Zip\7z.exe', 'x', '-aoa', '-y', archive]) + else: + subprocess.run([r'7z', 'x', '-aoa', '-y', archive]) + os.unlink(archive) + + @staticmethod + def get_base_dir(qt_version): + return os.path.join(os.getcwd(), 'Qt{}'.format(qt_version)) + + def install(self, qt_version, arch): + if arch.startswith('win'): + arch_dir = arch[6:] + else: + arch_dir = arch + base_dir = self.get_base_dir(qt_version) + if not os.path.exists(base_dir): + os.mkdir(base_dir) + elif not os.path.isdir(base_dir): + os.unlink(base_dir) + os.mkdir(base_dir) + os.chdir(base_dir) + + p = Pool(NUM_PROCESS) + archives = self.qt_archives.get_archives() + p.map(self.retrieve_archive, archives) + + try: + # prepare qt.conf + with open(os.path.join(base_dir, qt_version, arch_dir, 'bin', 'qt.conf'), 'w') as f: + f.write("[Paths]\n") + f.write("Prefix=..\n") + # prepare qtconfig.pri + with open(os.path.join(base_dir, qt_version, arch_dir, 'mkspecs', 'qconfig.pri'), 'r+') as f: + lines = f.readlines() + f.seek(0) + f.truncate() + for line in lines: + if 'QT_EDITION' in line: + line = 'QT_EDITION = OpenSource' + f.write(line) + except: + pass + diff --git a/qli-install b/aqtinst old mode 100755 new mode 100644 similarity index 56% rename from qli-install rename to aqtinst index 2036bd4..dd6ff88 --- a/qli-install +++ b/aqtinst @@ -1,8 +1,8 @@ -#!/bin/env python3 +#!/usr/bin/env python3 import sys -from qli-installer import main +from aqt import main if __name__ == '__main__': sys.exit(main()) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index a291667..562dade 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -12,12 +12,18 @@ jobs: versionSpec: '3.6' architecture: 'x64' - script: | + python -m pip install six flake8 + flake8 . + displayName: 'Run lint tests' + os_name = args.host + target = args.target +- script: | sudo apt-get update sudo apt-get -y install p7zip-full - task: PythonScript@0 inputs: scriptSource: filePath - scriptPath: $(Build.SourcesDirectory)/qli-installer.py + scriptPath: $(Build.SourcesDirectory)/aqtinst arguments: $(qtversion) linux desktop workingDirectory: $(Build.BinariesDirectory) displayName: install qt @@ -32,10 +38,12 @@ jobs: versionSpec: '3.6' architecture: 'x64' - script: brew install p7zip + - script: pip install -r requirements.txt + displayName: 'Install requirements' - task: PythonScript@0 inputs: scriptSource: filePath - scriptPath: $(Build.SourcesDirectory)/qli-installer.py + scriptPath: $(Build.SourcesDirectory)/aqtinst arguments: $(qtversion) mac desktop workingDirectory: $(Build.BinariesDirectory) displayName: install qt @@ -50,10 +58,12 @@ jobs: versionSpec: '3.6' architecture: 'x64' - script: cinst -y 7zip + - script: pip install -r requirements.txt + displayName: 'Install requirements' - task: PythonScript@0 inputs: scriptSource: filePath - scriptPath: $(Build.SourcesDirectory)/qli-installer.py + scriptPath: $(Build.SourcesDirectory)/aqtinst arguments: $(qtversion) windows desktop win64_msvc2017_64 workingDirectory: $(Build.BinariesDirectory) displayName: install qt diff --git a/qli-installer.py b/qli-installer.py deleted file mode 100755 index 5f049dd..0000000 --- a/qli-installer.py +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018 Linus Jahn -# Copyright (C) 2019 Hiroshi Miura -# -# 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 argparse -import os -import platform -import subprocess -import sys -import urllib.request -import xml.etree.ElementTree as ElementTree - -from argparse import RawTextHelpFormatter -from multiprocessing.dummy import Pool - - -BASE_URL = "https://download.qt.io/online/qtsdkrepository/" -NUM_PROCESS = 3 - - -class QtPackage: - name = "" - url = "" - archive = "" - desc = "" - - def __init__(self, name, archive_url, archive, package_desc): - self.name = name - self.url = archive_url - self.archive = archive - self.desc = package_desc - - def get_name(self): - return self.name - - def get_url(self): - return self.url - - def get_archive(self): - return self.archive - - def get_desc(self): - return self.desc - - -class QtArchives: - archives = [] - - def __init__(self, os_name, qt_version, target, arch): - qt_ver_num = qt_version.replace(".", "") - archive_url = BASE_URL - if os_name == "windows": - archive_url += os_name + "_x86/" - else: - archive_url += os_name + "_x64/" - archive_url += target + "/" + "qt5_" + qt_ver_num + "/" - # Get packages index - update_xml_url = archive_url + "Updates.xml" - content = urllib.request.urlopen(update_xml_url).read() - self.update_xml = ElementTree.fromstring(content) - for packageupdate in self.update_xml.iter("PackageUpdate"): - if packageupdate.find("DownloadableArchives").text is None: - continue - name = packageupdate.find("Name").text - downloadable_archives = packageupdate.find("DownloadableArchives").text.split(", ") - full_version = packageupdate.find("Version").text - package_desc = packageupdate.find("Description").text - for archive in downloadable_archives: - package_url = archive_url + name + "/" + full_version + archive - self.archives.append(QtPackage(name, package_url, archive, package_desc)) - - if len(self.archives)==0: - print("Error while parsing package information!") - exit(1) - - def get_archives(self): - return self.archives - - -class QtInstaller: - def __init__(self, qt_archives): - self.qt_archives = qt_archives - - def retrieve_archive(self, package): - archive = package.get_archive() - url = package.get_url() - sys.stdout.write("\033[K") - print("-Downloading {}...".format(url)) - urllib.request.urlretrieve(url, archive) - sys.stdout.write("\033[K") - print("-Extracting {}...".format(archive)) - if platform.system() is 'Windows': - subprocess.run([r'C:\Program Files\7-Zip\7z.exe', 'x', '-aoa', '-y', archive]) - else: - subprocess.run([r'7z', 'x', '-aoa', '-y', archive]) - os.unlink(archive) - - def get_base_dir(self, qt_version): - return os.path.join(os.getcwd(), 'Qt{}'.format(qt_version)) - - def install(self, qt_version, arch): - if arch.startswith('win'): - arch_dir = arch[6:] - else: - arch_dir = arch - base_dir = self.get_base_dir(qt_version) - if not os.path.exists(base_dir): - os.mkdir(base_dir) - elif not os.path.isdir(base_dir): - os.unlink(base_dir) - os.mkdir(base_dir) - os.chdir(base_dir) - - p = Pool(NUM_PROCESS) - archives = self.qt_archives.get_archives() - p.map(self.retrieve_archive, archives) - - try: - # prepare qt.conf - with open(os.path.join(base_dir, qt_version, arch_dir, 'bin', 'qt.conf'), 'w') as f: - f.write("[Paths]\n") - f.write("Prefix=..\n") - # prepare qtconfig.pri - with open(os.path.join(base_dir, qt_version, arch_dir, 'mkspecs', 'qconfig.pri'), 'r+') as f: - lines = f.readlines() - f.seek(0) - f.truncate() - for line in lines: - if 'QT_EDITION' in line: - line = 'QT_EDITION = OpenSource' - f.write(line) - except: - pass - -def show_help(): - print("Usage: {} []\n".format(sys.argv[0])) - print("qt-version: Qt version in the format of \"5.X.Y\"") - print("host systems: linux, mac, windows") - print("targets: desktop, android, ios") - print("arch: ") - print(" target linux/desktop: gcc_64") - print(" target mac/desktop: clang_64") - print(" target mac/ios: ios") - print(" windows/desktop: win64_msvc2017_64, win64_msvc2015_64") - print(" in32_msvc2015, win32_mingw53") - print(" android: android_x86, android_armv7") - exit(1) - -def main(): - parser = argparse.ArgumentParser(description='Install Qt SDK.', formatter_class=RawTextHelpFormatter, add_help=True) - parser.add_argument("qt_version", help="Qt version in the format of \"5.X.Y\"") - parser.add_argument('host', choices=['linux', 'mac', 'windows'], help="host os name") - parser.add_argument('target', choices=['desktop', 'android', 'ios'], help="target sdk") - parser.add_argument('arch', nargs='?', help="\ntarget linux/desktop: gcc_64" - "\ntarget mac/desktop: clang_64" - "\ntarget mac/ios: ios" - "\nwindows/desktop: win64_msvc2017_64, win64_msvc2015_64" - "\n in32_msvc2015, win32_mingw53" - "\nandroid: android_x86, android_armv7") - args = parser.parse_args() - arch = args.arch - if arch is None: - if os_name == "linux" and target == "desktop": - arch = "gcc_64" - elif os_name == "mac" and target == "desktop": - arch = "clang_64" - elif os_name == "mac" and target == "ios": - arch = "ios" - if arch == "": - print("Please supply a target architecture.") - args.print_help() - exit(1) - - qt_version = args.qt_version - os_name = args.host - target = args.target - - archives = QtArchives(os_name, qt_version, target, arch) - installer = QtInstaller(archives) - installer.install(qt_version, arch) - - sys.stdout.write("\033[K") - print("Finished installation") - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..64c56a3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +six \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..f4b6001 --- /dev/null +++ b/setup.py @@ -0,0 +1,22 @@ +#!/usr/bin/env python + +import io +import os + +from setuptools import setup + +def readme(): + with io.open(os.path.join(os.path.dirname(__file__),'README.rst'), mode="r", encoding="UTF-8") as f: + return f.read() + +setup(name='aqtinstall', + version='0.2.0', + description='Another Qt installer', + url='http://github.com/miurahr/qli-installer', + license='MIT', + long_description=readme(), + author='Hioshi Miura', + author_email='miurahr@linux.com', + packages = ["aqt"], + scripts = ["aqtinst"] +)