def __init__( self, os_name: str, target: str, version_str: str, arch: str, base: str, subarchives: Optional[Iterable[str]] = None, modules: Optional[Iterable[str]] = None, all_extra: bool = False, is_include_base_package: bool = True, timeout=(5, 5), ): self.version: Version = Version(version_str) self.target: str = target self.arch: str = arch self.os_name: str = os_name self.all_extra: bool = all_extra self.arch_list: List[str] = [item.get("arch") for item in Settings.qt_combinations] self.base: str = posixpath.join(base, "online/qtsdkrepository") self.logger = getLogger("aqt.archives") self.archives: List[QtPackage] = [] self.mod_list: Iterable[str] = modules or [] self.is_include_base_package: bool = is_include_base_package self.timeout = timeout try: self._get_archives() except ArchiveDownloadError as e: self.handle_missing_updates_xml(e) should_install_all_archives = subarchives is None if not should_install_all_archives: self.archives = list(filter(lambda a: a.name in subarchives, self.archives))
def test_cli_invalid_version(capsys, invalid_version): """Checks that invalid version strings are handled properly""" # Ensure that invalid_version cannot be a semantic_version.Version with pytest.raises(ValueError): Version(invalid_version) cli = Cli() cli._setup_settings() matcher = re.compile(r"^INFO : aqtinstall\(aqt\) v.* on Python 3.*\n" r"(.*\n)*" r"ERROR :.*Invalid version: '" + invalid_version + r"'! Please use the form '5\.X\.Y'\.\n.*") for cmd in ( ("install", invalid_version, "mac", "desktop"), ("doc", invalid_version, "mac", "desktop"), ("list-qt", "mac", "desktop", "--arch", invalid_version), ): cli = Cli() assert cli.run(cmd) == 1 out, err = capsys.readouterr() sys.stdout.write(out) sys.stderr.write(err) assert matcher.match(err)
def __init__( self, os_name: str, target: str, version_str: str, arch: str, base: str, subarchives: Optional[Iterable[str]] = None, modules: Optional[Iterable[str]] = None, all_extra: bool = False, is_include_base_package: bool = True, timeout=(5, 5), ): self.version: Version = Version(version_str) self.target: str = target self.arch: str = arch self.os_name: str = os_name self.all_extra: bool = all_extra self.arch_list: List[str] = [ item.get("arch") for item in Settings.qt_combinations ] self.base: str = base self.logger = getLogger("aqt.archives") self.archives: List[QtPackage] = [] self.subarchives: Optional[Iterable[str]] = subarchives self.mod_list: Iterable[str] = modules or [] self.is_include_base_package: bool = is_include_base_package self.timeout = timeout try: self._get_archives() except ArchiveDownloadError as e: self.handle_missing_updates_xml(e)
def test_list_architectures_and_modules(monkeypatch, version: str, extension: str, in_file: str, expect_out_file: str): archive_id = ArchiveId("qt", "windows", "desktop", extension) _xml = (Path(__file__).parent / "data" / in_file).read_text("utf-8") expect = json.loads( (Path(__file__).parent / "data" / expect_out_file).read_text("utf-8")) monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: _xml) for arch in expect["architectures"]: modules = MetadataFactory(archive_id).fetch_modules( Version(version), arch) assert modules == sorted(expect["modules_by_arch"][arch]) arches = MetadataFactory(archive_id).fetch_arches(Version(version)) assert arches == expect["architectures"]
def _set_arch(arch: Optional[str], os_name: str, target: str, qt_version_or_spec: str) -> str: """Choose a default architecture, if one can be determined""" if arch is not None and arch != "": return arch if os_name == "linux" and target == "desktop": return "gcc_64" elif os_name == "mac" and target == "desktop": return "clang_64" elif os_name == "mac" and target == "ios": return "ios" elif target == "android": try: if Version(qt_version_or_spec) >= Version("5.14.0"): return "android" except ValueError: pass raise CliInputError("Please supply a target architecture.", should_show_help=True)
def test_list_src_doc_examples_modules(monkeypatch, win_5152_sde_xml_file, cmd_type: str, host: str, version: str, expected: Set[str]): monkeypatch.setattr(MetadataFactory, "fetch_http", lambda self, _: win_5152_sde_xml_file) archive_id = ArchiveId("qt", host, "desktop", "src_doc_examples") modules = set( MetadataFactory(archive_id).fetch_modules_sde(cmd_type, Version(version))) assert modules == expected
def default_desktop_arch_dir(host: str, version: Union[Version, str]) -> str: version: Version = version if isinstance(version, Version) else Version(version) if host == "linux": return "gcc_64" elif host == "mac": return "macos" if version in SimpleSpec(">=6.1.2") else "clang_64" else: # Windows # This is a temporary solution. This arch directory cannot exist for many versions of Qt. # TODO: determine this dynamically return "mingw81_64"
def _validate_version_str(version_str: str, *, allow_latest: bool = False, allow_empty: bool = False): if (allow_latest and version_str == "latest") or (allow_empty and not version_str): return try: Version(version_str) except ValueError as e: raise CliInputError( f"Invalid version: '{version_str}'! Please use the form '5.X.Y'." ) from e
def _determine_qt_version(qt_version_or_spec: str, host: str, target: str, arch: str) -> Version: def choose_highest(x: Optional[Version], y: Optional[Version]) -> Optional[Version]: if x and y: return max(x, y) return x or y def opt_version_for_spec(ext: str, _spec: SimpleSpec) -> Optional[Version]: try: return MetadataFactory(ArchiveId("qt", host, target, ext), spec=_spec).getList().latest() except AqtException: return None try: return Version(qt_version_or_spec) except ValueError: pass try: spec = SimpleSpec(qt_version_or_spec) except ValueError as e: raise CliInputError( f"Invalid version or SimpleSpec: '{qt_version_or_spec}'\n" + SimpleSpec.usage()) from e else: version: Optional[Version] = None for ext in QtRepoProperty.possible_extensions_for_arch(arch): version = choose_highest(version, opt_version_for_spec(ext, spec)) if not version: raise CliInputError( f"No versions of Qt exist for spec={spec} with host={host}, target={target}, arch={arch}" ) getLogger("aqt.installer").info( f"Resolved spec '{qt_version_or_spec}' to {version}") return version
def test_qt_archives_modules(monkeypatch, arch, requested_module_names, has_nonexistent_modules: bool): def _mock(self, *args): return (Path(__file__).parent / "data" / "windows-5140-update.xml").read_text("utf-8") monkeypatch.setattr(QtArchives, "_download_update_xml", _mock) expect_json = json.loads((Path(__file__).parent / "data" / "windows-5140-expect.json").read_text("utf-8")) expected = expect_json["modules_metadata_by_arch"][arch] base_expected = expect_json["qt_base_pkgs_by_arch"][arch] os_name, target, base, version = "windows", "desktop", "https://example.com", Version( "5.14.0") qt_base = "QT-BASE" def locate_module_data(haystack: Iterable[Dict[str, str]], name: str) -> Dict[str, str]: if name == qt_base: return base_expected for mod_meta in haystack: if mod_meta["Name"] == f"qt.qt5.5140.{name}.{arch}": return mod_meta return {} def verify_qt_package_stride(pkgs: Iterable[QtPackage], expect: Dict[str, str]): # https://download.qt.io/online/qtsdkrepository/windows_x86/desktop/qt5_5140/ # qt.qt5.5140.qtcharts.win32_mingw73/5.14.0-0-202108190846qtcharts-windows-win32_mingw73.7z url_begin = "online/qtsdkrepository/windows_x86/desktop/qt5_5140" expected_archive_url_pattern = re.compile( r"^" + re.escape(url_begin) + "/(" + expect["Name"] + ")/" + re.escape(expect["Version"]) + r"(.+\.7z)$") expected_7z_files = set(expect["DownloadableArchives"]) for pkg in pkgs: if not expect["Description"]: assert not pkg.package_desc else: assert pkg.package_desc == expect["Description"] url_match = expected_archive_url_pattern.match(pkg.archive_path) assert url_match mod_name, archive_name = url_match.groups() assert pkg.archive == archive_name assert archive_name in expected_7z_files expected_7z_files.remove(archive_name) assert len(expected_7z_files ) == 0, "Actual number of packages was fewer than expected" if has_nonexistent_modules: expect_help = [ f"Please use 'aqt list-qt {os_name} {target} --modules {version} <arch>' to show modules available." ] for unexpected_module in requested_module_names: with pytest.raises(NoPackageFound) as e: mod_names = ("qtcharts", unexpected_module) QtArchives(os_name, target, str(version), arch, base, modules=mod_names) assert e.type == NoPackageFound assert unexpected_module in str( e.value), "Message should include the missing module" assert e.value.suggested_action == expect_help return is_all_modules = "all" in requested_module_names qt_pkgs = QtArchives(os_name, target, str(version), arch, base, modules=requested_module_names, all_extra=is_all_modules).archives if is_all_modules: requested_module_names = [ module["Name"].split(".")[-2] for module in expected ] unvisited_modules = {*requested_module_names, qt_base} # This assumes that qt_pkgs are in a specific order for pkg_update_name, qt_packages in groupby(qt_pkgs, lambda x: x.pkg_update_name): match = re.match( r"^qt\.qt5\.5140(\.addons\.\w+|\.\w+|)\." + arch + r"$", pkg_update_name) assert match, f"QtArchive includes package named '{pkg_update_name}' with unexpected naming convention" mod_name = match.group(1) mod_name = mod_name[1:] if mod_name else qt_base assert mod_name in unvisited_modules unvisited_modules.remove(mod_name) expected_meta = locate_module_data(expected, mod_name) verify_qt_package_stride(qt_packages, expected_meta) assert len(unvisited_modules ) == 0, f"Failed to produce packages for {unvisited_modules}"
def dir_for_version(ver: Version) -> str: return "5.9" if ver == Version( "5.9.0") else f"{ver.major}.{ver.minor}.{ver.patch}"
hash_filename = str(urlparse(url).path.split("/")[-1]) assert hash_filename.endswith(".sha256") filename = hash_filename[: -len(".sha256")] return f"{received_hash} {filename}" monkeypatch.setattr("aqt.helper.getUrl", mock_getUrl) with pytest.raises(ChecksumDownloadFailure) as e: get_hash("online/qtsdkrepository/some/path/to/archive.7z", "sha256", (5, 5)) assert e.type == ChecksumDownloadFailure @pytest.mark.parametrize( "version, expect", [ ("1.33.1", Version(major=1, minor=33, patch=1)), ( "1.33.1-202102101246", Version(major=1, minor=33, patch=1, build=("202102101246",)), ), ( "1.33-202102101246", Version(major=1, minor=33, patch=0, build=("202102101246",)), ), ("2020-05-19-1", Version(major=2020, minor=0, patch=0, build=("05-19-1",))), ], ) def test_helper_to_version_permissive(version, expect): assert Version.permissive(version) == expect
QtRepoProperty, SimpleSpec, ToolData, Version, Versions, show_list, suggested_follow_up, ) Settings.load_settings() @pytest.mark.parametrize( "arch, version, expect", ( ("wasm_32", Version("5.13.1"), "wasm"), ("mingw", Version("5.13.1"), ""), ("android_fake", Version("5.13.1"), ""), ("android_x86", Version("5.13.1"), ""), ("android_x86", Version("6.13.1"), "x86"), ("android_x86", Version("6.0.0"), "x86"), ), ) def test_list_extension_for_arch(arch: str, version: Version, expect: str): ext = QtRepoProperty.extension_for_arch(arch, version >= Version("6.0.0")) assert ext == expect @pytest.mark.parametrize( "arch, expect", (
"clang_64") def test_cli_check_version(): cli = Cli() cli._setup_settings() assert cli._check_qt_arg_versions("5.12.0") assert not cli._check_qt_arg_versions("5.12") @pytest.mark.parametrize( "host, target, arch, version_or_spec, expected_version, is_bad_spec", ( ("windows", "desktop", "wasm_32", "6.1", None, False), ("windows", "desktop", "wasm_32", "5.12", None, False), ("windows", "desktop", "wasm_32", "5.13", Version("5.13.2"), False), ("windows", "desktop", "wasm_32", "5", Version("5.15.2"), False), ("windows", "desktop", "wasm_32", "<5.14.5", Version("5.14.2"), False), ("windows", "desktop", "mingw32", "6.0", Version("6.0.3"), False), ("windows", "winrt", "mingw32", "6", None, False), ("windows", "winrt", "mingw32", "bad spec", None, True), ("windows", "android", "android_x86", "6", Version("6.1.0"), False), ("windows", "desktop", "android_x86", "6", Version("6.1.0"), False), # does not validate arch ("windows", "desktop", "android_fake", "6", Version("6.1.0"), False), # does not validate arch ), ) def test_cli_determine_qt_version(monkeypatch, host, target, arch, version_or_spec: str, expected_version: Version,
def version(self): return Version.permissive(self.full_version)
def _version_str(self) -> str: return ("{0.major}{0.minor}" if self.version == Version("5.9.0") else "{0.major}{0.minor}{0.patch}").format( self.version )
def _arch_ext(self) -> str: ext = QtRepoProperty.extension_for_arch(self.arch, self.version >= Version("6.0.0")) return ("_" + ext) if ext else ""
def test_list_extension_for_arch(arch: str, version: Version, expect: str): ext = QtRepoProperty.extension_for_arch(arch, version >= Version("6.0.0")) assert ext == expect
def test_helper_to_version_permissive(version, expect): assert Version.permissive(version) == expect
def update(cls, target: TargetConfig, base_dir: str): """ Make Qt configuration files, qt.conf and qtconfig.pri. And update pkgconfig and patch Qt5Core and qmake """ logger = getLogger("aqt.updater") arch = target.arch version = Version(target.version) os_name = target.os_name version_dir = "5.9" if version == Version("5.9.0") else target.version if arch is None: arch_dir = "" elif arch.startswith("win64_mingw"): arch_dir = arch[6:] + "_64" elif arch.startswith("win32_mingw"): arch_dir = arch[6:] + "_32" elif arch.startswith("win"): m = re.match(r"win\d{2}_(msvc\d{4})_(winrt_x\d{2})", arch) if m: a, b = m.groups() arch_dir = b + "_" + a else: arch_dir = arch[6:] elif version in SimpleSpec( ">=6.1.2") and os_name == "mac" and arch == "clang_64": arch_dir = "macos" else: arch_dir = arch try: prefix = pathlib.Path(base_dir) / version_dir / arch_dir updater = Updater(prefix, logger) updater.set_license(base_dir, version_dir, arch_dir) if target.arch not in [ "ios", "android", "wasm_32", "android_x86_64", "android_arm64_v8a", "android_x86", "android_armv7", ]: # desktop version updater.make_qtconf(base_dir, version_dir, arch_dir) updater.patch_qmake() if target.os_name == "linux": updater.patch_pkgconfig("/home/qt/work/install", target.os_name) updater.patch_libtool("/home/qt/work/install/lib", target.os_name) elif target.os_name == "mac": updater.patch_pkgconfig("/Users/qt/work/install", target.os_name) updater.patch_libtool("/Users/qt/work/install/lib", target.os_name) elif target.os_name == "windows": updater.make_qtenv2(base_dir, version_dir, arch_dir) if version < Version("5.14.0"): updater.patch_qtcore(target) elif version in SimpleSpec(">=5.0,<6.0"): updater.patch_qmake() else: # qt6 non-desktop updater.patch_qmake_script(base_dir, version_dir, target.os_name) updater.patch_target_qt_conf(base_dir, version_dir, arch_dir, target.os_name) except IOError as e: raise UpdaterError(f"Updater caused an IO error: {e}") from e