Repository: miurahr/aqtinstall
Pull request #314 (2021-07-14 18:39:12)

Extension validation in ListCommand
In the current implementation, if a user runs `aqt list qt6 windows android --modules 6.2.0`, without an extension, aqt prints this:
```
No modules available for this request.
Please use 'aqt qt6 windows android' to show versions of Qt available
```

This happens because all the Qt6 Android modules require an extension for the processor architecture. The current error message does nothing to indicate what the problem is. The user's request seems perfectly reasonable

# What this PR does:
This change adds a function that checks for obvious misuses and omissions of the `--extension` flag, according to these rules:

1. On Qt6 for Android, an extension for Android processor architecture ("x86_64", "x86", "armv7", "arm64_v8a") is required.
2. On any platform other than Android, or on Qt5, the extensions for Android processor architecture are not allowed.
3. The "wasm" extension only works on desktop targets for Qt 5.13-5.15.

This validation function is only called on requests for 'modules' and 'architectures', because these are the only places where the version of Qt is known and the extension matters. Hopefully, this change will reduce confusion among users.




Patch:
diff --git a/aqt/metadata.py b/aqt/metadata.py
index 7bf51d0..36771e4 100644
--- a/aqt/metadata.py
+++ b/aqt/metadata.py
@@ -214,6 +214,7 @@ class ArchiveId:
         "armv7",
         "arm64_v8a",
     )
+    EXTENSIONS_REQUIRED_ANDROID_QT6 = "x86_64", "x86", "armv7", "arm64_v8a"
 
     def __init__(self, category: str, host: str, target: str, extension: str = ""):
         if category not in ArchiveId.CATEGORIES:
@@ -461,6 +462,40 @@ class ListCommand:
         ]
         return Table(head, rows)
 
+    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.
+        """
+        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"
+            )
+        if "wasm" in self.archive_id.extension and (
+            qt_ver not in SimpleSpec(">=5.13,<6") or self.archive_id.target != "desktop"
+        ):
+            raise CliInputError(
+                f"The extension '{self.archive_id.extension}' is only available in Qt 5.13 to 5.15 on desktop."
+            )
+
     @staticmethod
     def choose_highest_version_in_spec(
         all_tools_data: Dict[str, Dict[str, str]], simple_spec: SimpleSpec
@@ -578,6 +613,7 @@ class ListCommand:
         self, version: Version
     ) -> Tuple[List[str], List[str]]:
         """Returns [list of modules, list of architectures]"""
+        self.validate_extension(version)
         # NOTE: The url at `<base>/<host>/<target>/qt5_590/` does not exist; the real one is `qt5_590`
         patch = (
             ""


Test patch:
diff --git a/tests/test_list.py b/tests/test_list.py
index 45beeda..5c4b5d7 100644
--- a/tests/test_list.py
+++ b/tests/test_list.py
@@ -1,5 +1,6 @@
 import json
 import re
+import sys
 from pathlib import Path
 
 import pytest
@@ -279,3 +280,45 @@ def test_list_choose_tool_by_version(simple_spec, expected_name):
         assert item["Name"] == expected_name
     else:
         assert expected_name is None
+
+
+qt6_android_requires_ext_msg = (
+    "Qt 6 for Android requires one of the following extensions: "
+    f"{ArchiveId.EXTENSIONS_REQUIRED_ANDROID_QT6}. "
+    "Please add your extension using the `--extension` flag."
+)
+no_arm64_v8_msg = "The extension 'arm64_v8a' is only valid for Qt 6 for Android"
+no_wasm_msg = "The extension 'wasm' is only available in Qt 5.13 to 5.15 on desktop."
+
+
+@pytest.mark.parametrize(
+    "target, ext, version, expected_msg",
+    (
+        ("android", "", "6.2.0", qt6_android_requires_ext_msg),
+        ("android", "arm64_v8a", "5.13.0", no_arm64_v8_msg),
+        ("desktop", "arm64_v8a", "5.13.0", no_arm64_v8_msg),
+        ("desktop", "arm64_v8a", "6.2.0", no_arm64_v8_msg),
+        ("desktop", "wasm", "5.12.11", no_wasm_msg),  # out of range
+        ("desktop", "wasm", "6.2.0", no_wasm_msg),  # out of range
+        ("android", "wasm", "5.12.11", no_wasm_msg),  # in range, wrong target
+        ("android", "wasm", "5.14.0", no_wasm_msg),  # in range, wrong target
+        ("android", "wasm", "6.2.0", qt6_android_requires_ext_msg),
+    ),
+)
+def test_list_invalid_extensions(
+    capsys, monkeypatch, target, ext, version, expected_msg
+):
+    def _mock(_, rest_of_url: str) -> str:
+        return ""
+
+    monkeypatch.setattr(ListCommand, "fetch_http", _mock)
+
+    cat = "qt" + version[0]
+    host = "windows"
+    extension_params = ["--extension", ext] if ext else []
+    cli = Cli()
+    cli.run(["list", cat, host, target, *extension_params, "--arch", version])
+    out, err = capsys.readouterr()
+    sys.stdout.write(out)
+    sys.stderr.write(err)
+    assert expected_msg in err