Example #1
0
    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."
            )
Example #2
0
    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(", ")]))
Example #3
0
    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)
Example #4
0
 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
Example #5
0
    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
Example #6
0
    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
Example #7
0
    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)
Example #8
0
    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
Example #9
0
 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)
Example #10
0
 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))
Example #11
0
    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
Example #12
0
    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))
Example #13
0
        ),
    ),
)
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")