def validate_extension(self, qt_ver: Version) -> None: """ Checks extension, and raises CliInputError if invalid. Rules: 1. On Qt6 for Android, an extension for processor architecture is required. 2. On any platform other than Android, or on Qt5, an extension for processor architecture is forbidden. 3. The "wasm" extension only works on desktop targets for Qt 5.13-5.15, or for 6.2+ """ if ( self.archive_id.target == "android" and qt_ver.major == 6 and self.archive_id.extension not in ArchiveId.EXTENSIONS_REQUIRED_ANDROID_QT6 ): raise CliInputError( "Qt 6 for Android requires one of the following extensions: " f"{ArchiveId.EXTENSIONS_REQUIRED_ANDROID_QT6}. " "Please add your extension using the `--extension` flag." ) if self.archive_id.extension in ArchiveId.EXTENSIONS_REQUIRED_ANDROID_QT6 and ( self.archive_id.target != "android" or qt_ver.major != 6 ): raise CliInputError(f"The extension '{self.archive_id.extension}' is only valid for Qt 6 for Android") is_in_wasm_range = qt_ver in SimpleSpec(">=5.13,<6") or qt_ver in SimpleSpec(">=6.2.0") if "wasm" in self.archive_id.extension and (self.archive_id.target != "desktop" or not is_in_wasm_range): raise CliInputError( f"The extension '{self.archive_id.extension}' is only available in Qt 5.13-5.15 and 6.2+ on desktop." )
def fetch_archives(self, version: Version, arch: str, modules: List[str]) -> List[str]: qt_version_str = self._get_qt_version_str(version) nonempty = MetadataFactory._has_nonempty_downloads def all_modules(element: Element) -> bool: _module, _arch = element.find("Name").text.split(".")[-2:] return _arch == arch and _module != qt_version_str and nonempty(element) def specify_modules(element: Element) -> bool: _module, _arch = element.find("Name").text.split(".")[-2:] return _arch == arch and _module in modules and nonempty(element) def no_modules(element: Element) -> bool: name: Optional[str] = element.find("Name").text return name and name.endswith(f".{qt_version_str}.{arch}") and nonempty(element) predicate = no_modules if not modules else all_modules if "all" in modules else specify_modules try: mod_metadata = self._fetch_module_metadata(self.archive_id.to_folder(qt_version_str), predicate=predicate) except (AttributeError,) as e: raise ArchiveListError(f"Downloaded metadata is corrupted. {e}") from e # Did we find all requested modules? if modules and "all" not in modules: requested_set = set(modules) actual_set = set([_name.split(".")[-2] for _name in mod_metadata.keys()]) not_found = sorted(requested_set.difference(actual_set)) if not_found: raise CliInputError( f"The requested modules were not located: {not_found}", suggested_action=suggested_follow_up(self) ) csv_lists = [mod["DownloadableArchives"] for mod in mod_metadata.values()] return sorted(set([arc.split("-")[0] for csv in csv_lists for arc in csv.split(", ")]))
def run_list_qt(self, args: argparse.ArgumentParser): """Print versions of Qt, extensions, modules, architectures""" if not args.target: print(" ".join(ArchiveId.TARGETS_FOR_HOST[args.host])) return if args.target not in ArchiveId.TARGETS_FOR_HOST[args.host]: raise CliInputError( "'{0.target}' is not a valid target for host '{0.host}'". format(args)) if args.modules: modules_ver, modules_query = args.modules[0], tuple(args.modules) else: modules_ver, modules_query = None, None for version_str in (modules_ver, args.extensions, args.arch, args.archives[0] if args.archives else None): Cli._validate_version_str(version_str, allow_latest=True, allow_empty=True) spec = None try: if args.spec is not None: spec = SimpleSpec(args.spec) except ValueError as e: raise CliInputError( f"Invalid version specification: '{args.spec}'.\n" + SimpleSpec.usage()) from e meta = MetadataFactory( archive_id=ArchiveId( "qt", args.host, args.target, args.extension if args.extension else "", ), spec=spec, is_latest_version=args.latest_version, modules_query=modules_query, extensions_ver=args.extensions, architectures_ver=args.arch, archives_query=args.archives, ) show_list(meta)
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 _to_version(self, qt_ver: str) -> Version: """ Turns a string in the form of `5.X.Y | latest` into a semantic version. If the string does not fit either of these forms, CliInputError will be raised. If qt_ver == latest, and no versions exist corresponding to the filters specified, then CliInputError will be raised. If qt_ver == latest, and an HTTP error occurs, requests.RequestException will be raised. :param qt_ver: Either the literal string `latest`, or a semantic version with each part separated with dots. """ assert qt_ver if qt_ver == "latest": latest_version = self.fetch_latest_version() if not latest_version: msg = "There is no latest version of Qt with the criteria '{}'".format(self.describe_filters()) raise CliInputError(msg) return latest_version try: version = Version(qt_ver) except ValueError as e: raise CliInputError(e) from e return version
def run_list_tool(self, args: argparse.ArgumentParser): """Print tools""" if not args.target: print(" ".join(ArchiveId.TARGETS_FOR_HOST[args.host])) return if args.target not in ArchiveId.TARGETS_FOR_HOST[args.host]: raise CliInputError( "'{0.target}' is not a valid target for host '{0.host}'". format(args)) meta = MetadataFactory( archive_id=ArchiveId("tools", args.host, args.target), tool_name=args.tool_name, is_long_listing=args.long, ) show_list(meta)
def _set_sevenzip(self, external): sevenzip = external if sevenzip is None: return None try: subprocess.run( [sevenzip, "--help"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) except FileNotFoundError as e: raise CliInputError( "Specified 7zip command executable does not exist: {!r}". format(sevenzip)) from e return sevenzip
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 run_install_src(self, args): """Run src subcommand""" if not hasattr(args, "qt_version"): args.qt_version = str( Cli._determine_qt_version(args.qt_version_spec, args.host, args.target, arch="")) if args.kde and args.qt_version != "5.15.2": raise CliInputError("KDE patch: unsupported version!!") start_time = time.perf_counter() self._run_src_doc_examples("src", args) if args.kde: if args.outputdir is None: target_dir = os.path.join(os.getcwd(), args.qt_version, "Src") else: target_dir = os.path.join(args.outputdir, args.qt_version, "Src") Updater.patch_kde(target_dir) self.logger.info("Time elapsed: {time:.8f} second".format( time=time.perf_counter() - start_time))
def __init__( self, archive_id: ArchiveId, *, spec: Optional[SimpleSpec] = None, is_latest_version: bool = False, modules_query: Optional[Tuple[str, str]] = None, extensions_ver: Optional[str] = None, architectures_ver: Optional[str] = None, archives_query: Optional[List[str]] = None, src_doc_examples_query: Optional[Tuple[str, Version, bool]] = None, tool_name: Optional[str] = None, is_long_listing: bool = False, ): """ Construct MetadataFactory. :param spec: When set, the MetadataFactory will filter out all versions of Qt that don't fit this SimpleSpec. :param is_latest_version: When True, the MetadataFactory will find all versions of Qt matching filters, and only print the most recent version :param modules_query: [Version of Qt, architecture] for which to list modules :param extensions_ver: Version of Qt for which to list extensions :param architectures_ver: Version of Qt for which to list architectures :param archives_query: [Qt_Version, architecture, *module_names]: used to print list of archives :param tool_name: Name of a tool, without architecture, ie "tools_qtcreator" or "tools_ifw" :param is_long_listing: If true, long listing is used for tools output """ self.logger = getLogger("aqt.metadata") self.archive_id = archive_id self.spec = spec if archive_id.is_tools(): if tool_name: if not tool_name.startswith("tools_"): tool_name = "tools_" + tool_name if is_long_listing: self.request_type = "tool long listing" self._action = lambda: self.fetch_tool_long_listing(tool_name) else: self.request_type = "tool variant names" self._action = lambda: self.fetch_tool_modules(tool_name) else: self.request_type = "tools" self._action = self.fetch_tools elif is_latest_version: self.request_type = "latest version" self._action = lambda: Versions(self.fetch_latest_version()) elif modules_query: self.request_type = "modules" version, arch = modules_query self._action = lambda: self.fetch_modules(self._to_version(version), arch) elif extensions_ver: self.request_type = "extensions" self._action = lambda: self.fetch_extensions(self._to_version(extensions_ver)) elif architectures_ver: self.request_type = "architectures" self._action = lambda: self.fetch_arches(self._to_version(architectures_ver)) elif archives_query: if len(archives_query) < 2: raise CliInputError("The '--archives' flag requires a 'QT_VERSION' and an 'ARCHITECTURE' parameter.") self.request_type = "archives for modules" if len(archives_query) > 2 else "archives for qt" version, arch, modules = archives_query[0], archives_query[1], archives_query[2:] self._action = lambda: self.fetch_archives(self._to_version(version), arch, modules) elif src_doc_examples_query: cmd_type, version, is_modules_query = src_doc_examples_query if is_modules_query: self.request_type = f"modules for {cmd_type}" self._action = lambda: self.fetch_modules_sde(cmd_type, version) else: self.request_type = f"archives for {cmd_type}" self._action = lambda: self.fetch_archives_sde(cmd_type, version) else: self.request_type = "versions" self._action = self.fetch_versions
def run_install_qt(self, args): """Run install subcommand""" start_time = time.perf_counter() self.show_aqt_version() if args.is_legacy: self._warn_on_deprecated_command("install", "install-qt") target: str = args.target os_name: str = args.host arch: str = self._set_arch( args.arch, os_name, target, getattr(args, "qt_version", getattr(args, "qt_version_spec", None))) if hasattr(args, "qt_version_spec"): qt_version: str = str( Cli._determine_qt_version(args.qt_version_spec, os_name, target, arch)) else: qt_version: str = args.qt_version Cli._validate_version_str(qt_version) keep = args.keep output_dir = args.outputdir if output_dir is None: base_dir = os.getcwd() else: base_dir = output_dir if args.timeout is not None: timeout = (args.timeout, args.timeout) else: timeout = (Settings.connection_timeout, Settings.response_timeout) modules = args.modules sevenzip = self._set_sevenzip(args.external) if EXT7Z and sevenzip is None: # override when py7zr is not exist sevenzip = self._set_sevenzip("7z") if args.base is not None: if not self._check_mirror(args.base): raise CliInputError( "The `--base` option requires a url where the path `online/qtsdkrepository` exists.", should_show_help=True, ) base = args.base else: base = Settings.baseurl archives = args.archives if args.noarchives: if modules is None: raise CliInputError( "When `--noarchives` is set, the `--modules` option is mandatory." ) if archives is not None: raise CliInputError( "Options `--archives` and `--noarchives` are mutually exclusive." ) else: if modules is not None and archives is not None: archives.append(modules) nopatch = args.noarchives or ( archives is not None and "qtbase" not in archives) # type: bool if not self._check_qt_arg_versions(qt_version): self.logger.warning( "Specified Qt version is unknown: {}.".format(qt_version)) if not self._check_qt_arg_combination(qt_version, os_name, target, arch): self.logger.warning( "Specified target combination is not valid or unknown: {} {} {}" .format(os_name, target, arch)) all_extra = True if modules is not None and "all" in modules else False if not all_extra and not self._check_modules_arg(qt_version, modules): self.logger.warning("Some of specified modules are unknown.") qt_archives = self.retry_on_bad_connection( lambda base_url: QtArchives( os_name, target, qt_version, arch, base=base_url, subarchives=archives, modules=modules, all_extra=all_extra, is_include_base_package=not args.noarchives, timeout=timeout, ), base, ) target_config = qt_archives.get_target_config() run_installer(qt_archives.get_packages(), base_dir, sevenzip, keep) if not nopatch: Updater.update(target_config, base_dir) self.logger.info("Finished installation") self.logger.info("Time elapsed: {time:.8f} second".format( time=time.perf_counter() - start_time))
), ), ) def test_list_describe_filters(meta: MetadataFactory, expect: str): assert meta.describe_filters() == expect @pytest.mark.parametrize( "archive_id, spec, version_str, expect", ( (mac_qt, None, "5.12.42", Version("5.12.42")), ( mac_qt, None, "not a version", CliInputError("Invalid version string: 'not a version'"), ), (mac_qt, SimpleSpec("5"), "latest", Version("5.15.2")), ( mac_qt, SimpleSpec("5.0"), "latest", CliInputError( "There is no latest version of Qt with the criteria 'qt/mac/desktop with spec 5.0'" ), ), ), ) def test_list_to_version(monkeypatch, archive_id, spec, version_str, expect): _html = (Path(__file__).parent / "data" / "mac-desktop.html").read_text("utf-8")