Repository: miurahr/aqtinstall

Issue:
Feature: make the message about "unknown" Qt versions and modules more friendly and easy to understand
**Is your feature request related to a problem? Please describe.**

Like jurplel/install-qt-action#172, there are some users who are afraid of installing unknown Qt stuff to the CI environment or their computers. To reduce confusion, we'd better make the message more friendly and easy to understand.

**Describe the solution you'd like**

Just monkey-typing:

```diff
- Specified Qt version is unknown: 6.4.5.
+ Specified Qt version "6.4.5" may be released after this version of aqtinstall. We will try our best.
```

```diff
- Some of specified modules are unknown.
+ Specified modules "foo", "bar" and "baz" may be released after this version of aqtinstall. We will try our best.
```

**Describe alternatives you've considered**

Be polite, be gentle. :)

**Additional context**

None.

/cc @ddalcino 

Comments:

Patch:
diff --git a/aqt/installer.py b/aqt/installer.py
index c6cc0c7..4af89c8 100644
--- a/aqt/installer.py
+++ b/aqt/installer.py
@@ -213,6 +213,21 @@ class Cli:
     def _check_qt_arg_version_offline(self, version):
         return version in Settings.available_offline_installer_version
 
+    def _warning_unknown_qt_version(self, qt_version: str) -> str:
+        return self._warning_on_bad_combination(f'Qt version "{qt_version}"')
+
+    def _warning_unknown_target_arch_combo(self, args: List[str]) -> str:
+        return self._warning_on_bad_combination(f"target combination \"{' '.join(args)}\"")
+
+    def _warning_unexpected_modules(self, unexpected_modules: List[str]) -> str:
+        return self._warning_on_bad_combination(f"modules {unexpected_modules}")
+
+    def _warning_on_bad_combination(self, combo_message: str) -> str:
+        return (
+            f"Specified {combo_message} did not exist when this version of aqtinstall was released. "
+            "This may not install properly, but we will try our best."
+        )
+
     def _set_sevenzip(self, external):
         sevenzip = external
         if sevenzip is None:
@@ -257,13 +272,10 @@ class Cli:
             return False
         return True
 
-    def _check_modules_arg(self, qt_version, modules):
-        if modules is None:
-            return True
+    def _select_unexpected_modules(self, qt_version: str, modules: Optional[List[str]]) -> List[str]:
+        """Returns a sorted list of all the requested modules that do not exist in the combinations.json file."""
         available = Settings.available_modules(qt_version)
-        if available is None:
-            return False
-        return all([m in available for m in modules])
+        return sorted(set(modules or []) - set(available or []))
 
     @staticmethod
     def _determine_qt_version(
@@ -388,14 +400,14 @@ class Cli:
         auto_desktop_archives: List[QtPackage] = get_auto_desktop_archives()
 
         if not self._check_qt_arg_versions(qt_version):
-            self.logger.warning("Specified Qt version is unknown: {}.".format(qt_version))
+            self.logger.warning(self._warning_unknown_qt_version(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)
-            )
+            self.logger.warning(self._warning_unknown_target_arch_combo([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.")
+        if not all_extra:
+            unexpected_modules = self._select_unexpected_modules(qt_version, modules)
+            if unexpected_modules:
+                self.logger.warning(self._warning_unexpected_modules(unexpected_modules))
 
         qt_archives: QtArchives = retry_on_bad_connection(
             lambda base_url: QtArchives(
@@ -465,7 +477,7 @@ class Cli:
         archives = args.archives
         all_extra = True if modules is not None and "all" in modules else False
         if not self._check_qt_arg_versions(qt_version):
-            self.logger.warning("Specified Qt version is unknown: {}.".format(qt_version))
+            self.logger.warning(self._warning_unknown_qt_version(qt_version))
 
         srcdocexamples_archives: SrcDocExamplesArchives = retry_on_bad_connection(
             lambda base_url: SrcDocExamplesArchives(
@@ -562,7 +574,7 @@ class Cli:
 
         for arch in archs:
             if not self._check_tools_arg_combination(os_name, tool_name, arch):
-                self.logger.warning("Specified target combination is not valid: {} {} {}".format(os_name, tool_name, arch))
+                self.logger.warning(self._warning_unknown_target_arch_combo([os_name, tool_name, arch]))
 
             tool_archives: ToolArchives = retry_on_bad_connection(
                 lambda base_url: ToolArchives(


Test patch:
diff --git a/tests/test_cli.py b/tests/test_cli.py
index 055c949..91a568b 100644
--- a/tests/test_cli.py
+++ b/tests/test_cli.py
@@ -53,13 +53,23 @@ def test_cli_help(capsys):
     assert expected_help(out)
 
 
-def test_cli_check_module():
+@pytest.mark.parametrize(
+    "qt_version, modules, unexpected_modules",
+    (
+        ("5.11.3", ["qtcharts", "qtwebengine"], []),
+        ("5.11.3", ["not_exist"], ["not_exist"]),
+        ("5.11.3", ["qtcharts", "qtwebengine", "not_exist"], ["not_exist"]),
+        ("5.11.3", None, []),
+        ("5.15.0", ["Unknown"], ["Unknown"]),
+    ),
+)
+def test_cli_select_unexpected_modules(qt_version: str, modules: Optional[List[str]], unexpected_modules: List[str]):
     cli = Cli()
     cli._setup_settings()
-    assert cli._check_modules_arg("5.11.3", ["qtcharts", "qtwebengine"])
-    assert not cli._check_modules_arg("5.7", ["not_exist"])
-    assert cli._check_modules_arg("5.14.0", None)
-    assert not cli._check_modules_arg("5.15.0", ["Unknown"])
+    assert cli._select_unexpected_modules(qt_version, modules) == unexpected_modules
+
+    nonexistent_qt = "5.16.0"
+    assert cli._select_unexpected_modules(nonexistent_qt, modules) == sorted(modules or [])
 
 
 def test_cli_check_combination():
diff --git a/tests/test_install.py b/tests/test_install.py
index 9cae23d..81a650a 100644
--- a/tests/test_install.py
+++ b/tests/test_install.py
@@ -1052,7 +1052,8 @@ def test_install(
         (
             "install-qt windows desktop 5.16.0 win32_mingw73",
             None,
-            "WARNING : Specified Qt version is unknown: 5.16.0.\n"
+            'WARNING : Specified Qt version "5.16.0" did not exist when this version of aqtinstall was released. '
+            "This may not install properly, but we will try our best.\n"
             "ERROR   : Failed to locate XML data for Qt version '5.16.0'.\n"
             "==============================Suggested follow-up:==============================\n"
             "* Please use 'aqt list-qt windows desktop' to show versions available.\n",
@@ -1060,7 +1061,8 @@ def test_install(
         (
             "install-qt windows desktop 5.15.0 bad_arch",
             "windows-5150-update.xml",
-            "WARNING : Specified target combination is not valid or unknown: windows desktop bad_arch\n"
+            'WARNING : Specified target combination "windows desktop bad_arch" did not exist when this version of '
+            "aqtinstall was released. This may not install properly, but we will try our best.\n"
             "ERROR   : The packages ['qt_base'] were not found while parsing XML of package information!\n"
             "==============================Suggested follow-up:==============================\n"
             "* Please use 'aqt list-qt windows desktop --arch 5.15.0' to show architectures available.\n",
@@ -1068,7 +1070,8 @@ def test_install(
         (
             "install-qt windows desktop 5.15.0 win32_mingw73 -m nonexistent foo",
             "windows-5150-update.xml",
-            "WARNING : Some of specified modules are unknown.\n"
+            "WARNING : Specified modules ['foo', 'nonexistent'] did not exist when this version of aqtinstall "
+            "was released. This may not install properly, but we will try our best.\n"
             "ERROR   : The packages ['foo', 'nonexistent', 'qt_base'] were not found"
             " while parsing XML of package information!\n"
             "==============================Suggested follow-up:==============================\n"
@@ -1106,7 +1109,8 @@ def test_install(
         (
             "install-tool windows desktop tools_vcredist nonexistent",
             "windows-desktop-tools_vcredist-update.xml",
-            "WARNING : Specified target combination is not valid: windows tools_vcredist nonexistent\n"
+            'WARNING : Specified target combination "windows tools_vcredist nonexistent" did not exist when this version of '
+            "aqtinstall was released. This may not install properly, but we will try our best.\n"
             "ERROR   : The package 'nonexistent' was not found while parsing XML of package information!\n"
             "==============================Suggested follow-up:==============================\n"
             "* Please use 'aqt list-tool windows desktop tools_vcredist' to show tool variants available.\n",
@@ -1114,7 +1118,8 @@ def test_install(
         (
             "install-tool windows desktop tools_nonexistent nonexistent",
             None,
-            "WARNING : Specified target combination is not valid: windows tools_nonexistent nonexistent\n"
+            'WARNING : Specified target combination "windows tools_nonexistent nonexistent" did not exist when this '
+            "version of aqtinstall was released. This may not install properly, but we will try our best.\n"
             "ERROR   : Failed to locate XML data for the tool 'tools_nonexistent'.\n"
             "==============================Suggested follow-up:==============================\n"
             "* Please use 'aqt list-tool windows desktop' to show tools available.\n",