From 94498816bdf4147cd48a7057e6b048c544b3226c Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Thu, 22 Sep 2022 20:43:07 -0500 Subject: [PATCH] Addon Manager: Support setting min Python version --- src/Mod/AddonManager/Addon.py | 66 +++++++++++++++++++++++++--- src/Mod/AddonManager/AddonManager.py | 31 +++++++++++-- 2 files changed, 86 insertions(+), 11 deletions(-) diff --git a/src/Mod/AddonManager/Addon.py b/src/Mod/AddonManager/Addon.py index ba493fdf78..adaf5414d3 100644 --- a/src/Mod/AddonManager/Addon.py +++ b/src/Mod/AddonManager/Addon.py @@ -116,6 +116,7 @@ class Addon: self.internal_workbenches: Set[str] = set() # Required internal workbenches self.python_required: Set[str] = set() self.python_optional: Set[str] = set() + self.python_min_version = {"major": 3, "minor": 0} class ResolutionFailed(RuntimeError): """An exception type for dependency resolution failure.""" @@ -168,13 +169,14 @@ class Addon: self.installed_version = None # Each repo is also a node in a directed dependency graph (referenced by name so - # they cen be serialized): + # they can be serialized): self.requires: Set[str] = set() self.blocks: Set[str] = set() - # And maintains a list of required and optional Python dependencies from metadata.txt + # And maintains a list of required and optional Python dependencies self.python_requires: Set[str] = set() self.python_optional: Set[str] = set() + self.python_min_version = {"major": 3, "minor": 0} def __str__(self) -> str: result = f"FreeCAD {self.repo_type}\n" @@ -334,11 +336,50 @@ class Addon: return for dep in metadata.Depend: - # Simple version for now: eventually support all of the version params... - self.requires.add(dep["package"]) - FreeCAD.Console.PrintLog( - f"Package {self.name}: Adding dependency on {dep['package']}\n" - ) + if dep["package"].lower() == "python": + # We only support the "version_gte" attribute for Python itself + if "version_gte" in dep and "." in dep["version_gte"]: + split_version_string = dep["version_gte"].split(".") + if len(split_version_string) >= 2: + try: + self.python_min_version["major"] = int( + split_version_string[0] + ) + self.python_min_version["minor"] = int( + split_version_string[1] + ) + FreeCAD.Console.PrintLog( + f"Package {self.name}: Requires Python {split_version_string[0]}.{split_version_string[1]} or greater\n" + ) + except ValueError: + FreeCAD.Console.PrintWarning( + f"Package {self.name}: Invalid Python version requirment specified\n" + ) + elif "type" in dep: + if dep["type"] == "internal": + if dep["package"] in INTERNAL_WORKBENCHES: + self.requires.add(dep["package"]) + else: + FreeCAD.Console.PrintWarning( + translate( + "AddonsInstaller", + "{}: Unrecognized internal workbench '{}'", + ).format(self.name, dep["package"]) + ) + elif dep["type"] == "addon": + self.requires.add(dep["package"]) + elif dep["type"] == "python": + if "optional" in dep and dep["optional"].lower() == "true": + self.python_optional.add(dep["package"]) + else: + self.python_required.add(dep["package"]) + else: + # Automatic resolution happens later, once we have a complete list of Addons + self.requires.add(dep["package"]) + else: + # Automatic resolution happens later, once we have a complete list of Addons + self.requires.add(dep["package"]) + for dep in metadata.Conflict: self.blocks.add(dep["package"]) @@ -478,6 +519,17 @@ class Addon: deps.python_required |= self.python_requires deps.python_optional |= self.python_optional + + deps.python_min_version["major"] = max( + deps.python_min_version["major"], self.python_min_version["major"] + ) + if deps.python_min_version["major"] == 3: + deps.python_min_version["minor"] = max( + deps.python_min_version["minor"], self.python_min_version["minor"] + ) + else: + FreeCAD.Console.PrintWarning("Unrecognized Python version information") + for dep in self.requires: if dep in all_repos: if not dep in deps.required_external_addons: diff --git a/src/Mod/AddonManager/AddonManager.py b/src/Mod/AddonManager/AddonManager.py index e68e95a2b9..edba157107 100644 --- a/src/Mod/AddonManager/AddonManager.py +++ b/src/Mod/AddonManager/AddonManager.py @@ -28,6 +28,7 @@ import os import functools import shutil import stat +import sys import tempfile import hashlib import threading @@ -128,10 +129,10 @@ class CommandAddonManager: restart_required = False def __init__(self): - #FreeCADGui.addPreferencePage( + # FreeCADGui.addPreferencePage( # os.path.join(os.path.dirname(__file__), "AddonManagerOptions.ui"), # translate("AddonsInstaller", "Addon Manager"), - #) + # ) FreeCADGui.addPreferencePage( AddonManagerOptions, translate("AddonsInstaller", "Addon Manager"), @@ -374,7 +375,7 @@ class CommandAddonManager: self.dialog.buttonDevTools.hide() # Only shown if there are available Python package updates - #self.dialog.buttonUpdateDependencies.hide() + # self.dialog.buttonUpdateDependencies.hide() # connect slots self.dialog.rejected.connect(self.reject) @@ -934,7 +935,9 @@ class CommandAddonManager: def show_python_updates_dialog(self) -> None: if not hasattr(self, "manage_python_packages_dialog"): - self.manage_python_packages_dialog = PythonPackageManager(self.item_model.repos) + self.manage_python_packages_dialog = PythonPackageManager( + self.item_model.repos + ) self.manage_python_packages_dialog.show() def show_developer_tools(self) -> None: @@ -1077,6 +1080,7 @@ class CommandAddonManager: self.wbs.append(dep) # Check the Python dependencies: + self.python_min_version = deps.python_min_version self.python_required = [] for py_dep in deps.python_required: if py_dep not in self.python_required: @@ -1250,6 +1254,25 @@ class CommandAddonManager: if self.handle_disallowed_python(missing.python_required): return + # For now only look at the minor version, since major is always Python 3 + minor_required = missing.python_min_version["minor"] + if sys.version_info.minor < minor_required: + QtWidgets.QMessageBox.critical( + self.dialog, + translate("AddonsInstaller", "Incompatible Python version"), + translate( + "AddonsInstaller", + "This Addon (or one if its dependencies) requires Python {}.{}, and your system is running {}.{}. Installation cancelled.", + ).format( + missing.python_min_version["major"], + missing.python_min_version["minor"], + sys.version_info.major, + sys.version_info.minor, + ), + QtWidgets.QMessageBox.Cancel, + ) + return + good_packages = [] for dep in missing.python_optional: if dep in self.allowed_packages: